Update an item using a django model instance - python

Is it a good idea to update an item by saving a model instance with the same id if the fields that are different between the item and the instance are unknown?
Lets say there is an Person item in the database:
id: 4
name: Foo
surename: Bar
tel: 0000000000
Is it a good idea to update that item like:
p = Person(
name='Foo'
surename='Bar'
tel='0000000111'
)
old_p = Person.objects.get(name='Foo', surname='Bar')
p.id = old_p.id
p.save()

Is it a good idea to update an item by saving a model instance with the same id?
No. If you Person model has other fields, you will need to copy all these fields in the new record p. Otherwise these might be updated to the default values the Person model has, or to the incorrect values you passed to the p = Person(…) call. This makes it hard to maintain code: each time you add a new field to a model, you need to update all code fragments where you update a single field.
If you want to update a single field, you can do this with an .update(…) call [Django-doc]:
Person.objects.filter(name='Foo', surname='Bar').update(tel='0000000111')
or if you want to run triggers, and work with the .save(…) method of that model, you can retrieve the object, and update that specific field, so:
p = Person.objects.get(name='Foo', surname='Bar')
p.tel = '0000000111'
p.save()

Related

Read one2many field in inherited model

In odoo v13, the crm.lead model is inherited and modified by the sale_crm module.
In the sale_crm module, the model crm.lead is inherited and a one2many field is added, order_ids. This is an array of sales orders associated with the lead.
I am trying to inherit the crm.lead model, and create a new field that is computed using the order_ids field.
I added sale_crm in the manifest dependencies
I inherit the crm.lead model and attempt to concat the names of all the associated SOs:
class Co7Lead(models.Model):
_inherit = "crm.lead"
so_list = fields.Text(
compute='_get_sos_text',
string="Text list of associated SOs",
help="A comma separated list of SOs associated with this lead")
def _get_sos_text(self):
txt = ""
for order in super(Co7Lead, self).order_ids:
txt += order.name + ""
return txt
Unfortunately, this causes a stack overflow (haha!)
I believe I need to use .browse on the order_ids field but I'm not able to find any examples on how to do this.
The compute method must assign the computed value to the field. If it uses the values of other fields (order_ids.name), it should specify those fields using depends().
You don't need to use super here, self is a record set, so loop over it to access the value of order_ids for each record.
Example:
#api.depends('order_ids.name')
def _get_sos_text(self):
for lead in self:
lead.so_list = "\n".join(order.name for order in lead.order_ids)

Django bulk_update not working on default attributes

I want to change the default value of an attribute from a model in Django. So I want to update the existing values in the database. Strange enough, a bulk update doesn't change those values.
My model:
class UserSettings(models.Model):
offline_notification_filter = models.BooleanField(default=False)
My test
class TestSetOfflineNotificationMigration(APITestCase):
def test_set_offline_notification_filter_to_false(self):
user_settings_1 = UserSettingsFactory(offline_notification_filter=True)
user_settings_2 = UserSettingsFactory(offline_notification_filter=False)
user_settings_3 = UserSettingsFactory(offline_notification_filter=True)
user_settings_4 = UserSettingsFactory()
all_user_settings = UserSettings.objects.all()
for user_setting in all_user_settings:
user_setting.offline_notification_filter = False
UserSettings.objects.bulk_update(
all_user_settings, ["offline_notification_filter"]
)
self.assertEqual(user_settings_1.offline_notification_filter, False)
This test is failing because the the offlince_notification_filter is not updating. Anyone knows why not?
I think you are working with outdated instance, so you might need to user_settings_1.refresh_from_db().
bulk_update() is not necessarily needed in this case. Since this update operation is being performed directly on all objects of queryset, so just using update() will automatically make changes in a single operation.
Bulk update is more suited to operations where instances of different models are being updated.
However if you still prefer to use bulk_update, then here's the answer:
user_settings_1 is an in-memory instance here, whereas bulk_update operation has made the change in database. You need to refresh it from database.
As stated in the docs: https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.bulk_update
You need to provide an array of the models to update, and specify the fields you want to update.
I would suggest the following code:
class TestSetOfflineNotificationMigration(APITestCase):
def test_set_offline_notification_filter_to_false(self):
user_settings_1 = UserSettingsFactory(offline_notification_filter=True)
user_settings_2 = UserSettingsFactory(offline_notification_filter=False)
user_settings_3 = UserSettingsFactory(offline_notification_filter=True)
user_settings_4 = UserSettingsFactory()
all_user_settings = UserSettings.objects.all()
# Create list to append new models
new_user_settings = []
for user_setting in all_user_settings:
# Create new model with the updated fields
new_user_setting = UserSettings(offline_notification_filter=False)
# Append
new_user_settings.append(new_user_setting)
# Response is amount of models updated
q = UserSettings.objects.bulk_update(
new_user_settings, ["offline_notification_filter"]
)
self.assertEqual(q, 4)
If you have a long set of fields to update, you can update the fields with the _meta method and create the list of fields to update like:
# Get list of fields
fields = [f.name for f in UserSettings._meta.fields]
# delete the ones you don't want to update with Sets
list_of_fields = fields - {"id"}
q = UserSettings.objects.bulk_update(
new_user_settings, list_of_fields
)

Python / Django: Make a lookup / database query inside a model class

What is the right way to lookup a table and use its last value as a value in a new model instance? Something like this:
class MyClass(models.Model):
id = models.AutoField(primary_key=True)
obj = MyClass.objects.latest('id')
my_field = models.IntegerField(default=(obj+1))
I need a db column, which keeps track of the primary_key, but is independent of it and can also be modified. Also I need to use its value as default when creating new instances of the Model.
you can use custom constructor as described in the docs:
https://docs.djangoproject.com/en/2.2/ref/models/instances/
you will need to define the obj field either as integer(to store the id of the previous record) or as a foreign key(if you want to reference the previous db record). In the second case you will need to pass the name of the model to the ForeignKeyField constructor as string('MyClass') and not directly(MyClass).

Sort objects by first instance of related object, Django

I have a model which is an instance for the existence of an item (a ticket), and on each creation of a ticket I create a instance of another model, a record. Each record keeps track of who made a change to the ticket, and what they did with it, it basically keeps a record of what has happened with it. I want to tickets creator and creation date to be defined as the creator and creation date of the first activity made which points to it. (The first of the many in a many to one relation.
As is, I have a function which does this very simply:
def created_by(self):
records = Record.objects.filter(ticket=self.id).order_by('created_on')
return records[0].created_by
However I run into an issue with this when trying to sort a collection of tickets (which is logically most often going to be sorted by creation date). I cannot sort by a function using django's filter for queries.
I don't really want to store the redundant data in the database, and I'd rather have the record than not so all date items related to the ticket can be seen in the records. Idea's on how to make it so I can sort and search by this first instance of record? (Also need to search for the creator because some users can only see their own tickets, some can see all, some can see subsets)
Thanks!
Assuming the Record ticket field is a Foreign key to the Ticket model:
class Record (models.Model):
....
create_time = models.DateTimeField()
ticket = models.ForeignKey(Ticket,related_name='records')
You can replace the ModelManager (objects) of the Ticket model and override the get_queryset function:
class TicketManager(models.ModelManager):
def get_queryset():
return super(TicketManager, self).get_queryset().annotate(create_time=Min('records__create_time')).order_by('create_time')
class Ticket(models.Model):
.....
objects = TicketManager
Now every query like Ticket.objects.all() or Ticket.objects.filter(...) will be sorted by the create time

Get FK set for each instance in queryset

I have a tree-like Django model say named A, which been done by django-mptt.
class A(MPTTModel):
parent = TreeForeignKey('self')
this class automaticly has the 'children' manager, so i can easily get the subtree
There is another model, which have FK link to A:
class SomeModel(models.Model):
link_to_a = models.ForeignKey(A)
I know, that if i want to get SomeModel set of A instance i can do that:
a = A.objects.filter(blah)
a.somemodel_set.all()
and the question is:
what is the most pythonic way to fetch somemodel_set of each instance in some queryset under A model, i.e. i want 4 example this:
some_A_instance.children.all().get_all_somemodel_instances()
and get_all_somemodel_instances() should retrieve ziped queryset of sets for each children
Do you just need the related items in one list, or do you need to associate each set with their parent? If the former, you can get them all at once with this:
related_items = SomeModel.objects.filter(link_to_a=some_A_instance.children.all())
which will do one single query (with a subquery) to get everything.
Otherwise, you can use prefetch_related() to get all items' related sets in one go:
items = some_A_instance.children.all().prefetch_related('somemodel_set')
This should do:
[child.somemodel_set.all() for child in some_A_instance.children.all()]

Categories

Resources