3

I have a legacy project that saves models with save, bulk_create and other methods within the framework.

What is the best way to set a specific value for an attribute so that every time a record is saved the new value is also saved? This value is constructed based on other attributes of the instance that is being saved.

I pose this question because I'm not sure all ways that save is possible in Django except save and bulk_create and knowing that on bulk_create:

The model’s save() method will not be called, and the pre_save and post_save signals will not be sent.

https://docs.djangoproject.com/en/1.8/ref/models/querysets/#bulk-create

Julio Marins
  • 10,039
  • 8
  • 48
  • 54
  • what about when you set a default value for a field – Lemayzeur May 12 '18 at 00:30
  • the value is based on other values of the instance that is currently being saved – Julio Marins May 12 '18 at 00:31
  • as far I know, with `save()` method it's possible, but not sure with `bulk_create()` since some relevant functions wont' be called – Lemayzeur May 12 '18 at 00:34
  • perhaps when you are calling bulk_create then you can preprocess the data before calling bulk_create and assign the calculated value to field. list2=[] for e in list: list2.append(Entry(a=e["a"],calc=fn(a)) Entry.objects.bulk_create(list2) – Sayok88 May 12 '18 at 10:30
  • I guess the new attribute is used to query records and need to be stored in db, but if not, you can make a getter on the model and construct value when read by the application. – Daniel Backman May 15 '18 at 04:30

4 Answers4

2

As far as I know, there are 3 ways to create/update model instances (which are records in database tables):

  1. Using the model instance method save().
  2. Using the queryset methods create(), update(), get_or_create(), update_or_create() and bulk_create().
  3. Using raw SQL or other low-level ways.

If you intend to calculate the value of a field when saving, you could override all of the methods I listed above.

Signals (like pre_create) are not a complete solution because they don't get triggered when bulk_create() is used and so some instance could get saved without the calculated attribute.

There is no django way (that I know) to intercept the third point I mentioned (raw SQL).


You did not elaborate on your use case, but (depending on your table size and change frequency) maybe you could also try:

  1. run a periodical process (maybe using crontab) that updates the calculated field of all model instances.
  2. add a database trigger that calculates the field.

Legacy databases or systems or usually not fun to work with, so maybe you will have to settle for a sub-optimal solution.

Ralf
  • 16,086
  • 4
  • 44
  • 68
0

You can set default value in your model's field using custom functions. For example you have a Post model that also has a field slug. You want default value for slug field to be auto generated from name field. You can write your model like below:

class Post(models.Model):

    def generate_slug(self):
        return slugify(self.name)

    name = models.CharField()
    description = models.TextField()
    attachment = models.FileField()
    slug = models.CharField(default=generate_slug)

This way when you create a new post, the slug field will be auto generated from the name field.

SK. Fazlee Rabby
  • 344
  • 4
  • 14
  • with your code I got this: `TypeError: generate_slug() missing 1 required positional argument: 'self'` – Julio Marins May 12 '18 at 13:59
  • Make sure you are using `default=generate_slug` without the parenthesis. – SK. Fazlee Rabby May 12 '18 at 14:11
  • I'm using exactly what you answered. This error message is the last one in the error stack after I call `Post.objects.create(name="foo")` – Julio Marins May 12 '18 at 14:21
  • 1
    My bad. That only work with `upload_to` attributes in FileField. You can try to override the model's save method. You do have access to self inside `save()` method. Refer to this link please. https://stackoverflow.com/questions/4269605/django-override-save-for-model – SK. Fazlee Rabby May 12 '18 at 14:45
0

Another way to do that is to create a layer between your caller and the models(database layer) so you can add your logic there. With this you will narrow the possibilities to just the methods you expose in that layer and have control over what should happen everywhere in terms of database talk.

Julio Marins
  • 10,039
  • 8
  • 48
  • 54
-1

The best way to deal with this issue is to override the save method(). You can use as well raw sql queries , which can easily solve your problems as well

class Model(model.Model):
    field1=models.CharField()
    field2=models.CharField()
    field3=models.CharField()

    def myfunc (self):
    pass 

        #
    def save(self, *args, **kwargs):
        q =  MyModel.objects.select_related('fields1', 'field2',  'filed2').filter(related_field)
    super(Model, self).save(*args, **kwargs)