I have a form in which I take the data from some of the fields and create a new model object, then assign that newly created object a one-to-one relationship to a preexisting object of a different model. Here is my save method in the form.
def save(self, *args, **kwargs):
super(CustomerProfileForm, self).save(*args, **kwargs)
if self.cleaned_data['street_address']:
if not self.instance.customer.home_location:
home_location = Location()
else :
home_location = self.instance.customer.home_location
home_location.name = 'Home/Apartment'
home_location.street_address = self.cleaned_data['street_address']
home_location.city = self.cleaned_data['city']
home_location.state = self.cleaned_data['state']
home_location.zip_code = self.cleaned_data['zip_code']
self.instance.customer.home_location = home_location
home_location.save()
self.instance.customer.save()
return self.instance
The Location object is being created and populated with the information from the form as I expect, but the OneToOne relationship with the CustomerProfile object (self.instance) is not being assigned. Does anyone know why this might be?
This makes no sense to me. When I print self.instance.customer.home_location right before the end of the save function, the new location is logged to the console, which shows that the relationship is assigned... How does it get unassigned after the save method completes...?
In order to save a relationship, the object needs to have a primary key; and this is only generated after the object is saved.
Therefore, you need to save the object first, before assigning it as a foreign key:
home_location.save()
self.instance.customer.home_location = home_location
# home_location.save() - this line should come before any relationships
# are linked to the object.
self.instance.customer.save()
Related
I have fields in multiple related models whose values are fully derived from other fields both in the model being saved and from fields in related models. I wanted to automate their value maintenance so that they are always current/valid, so I wrote a base class that each model inherits from. It overrides the .save() and .delete().
It pretty much works except for when multiple updates are triggered via changes to a through model of a M:M relationship between models named Infusate and Tracer (the through model is named InfusateTracer). So for example, I have a test that creates 2 InfusateTracer model records, which triggers updates to Infusate:
glu_t = Tracer.objects.create(compound=glu)
c16_t = Tracer.objects.create(compound=c16)
io = Infusate.objects.create(short_name="ti")
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
print(f"Name: {infusate.name}")
Infusate.objects.get(name="ti{C16:0-[5,6-13C5,17O1];glucose-[2,3-13C5,4-17O1]}")
The save() override looks like this:
def save(self, *args, **kwargs):
# Set the changed value triggering this update so that the derived value of the automatically updated field reflects the new values:
super().save(*args, **kwargs)
# Update the fields that change due to the above change (if any)
self.update_decorated_fields()
# Note, I cannot call save again because I get a duplicate exception, so `update_decorated_fields` uses `setattr`:
# super().save(*args, **kwargs)
# Percolate changes up to the parents (if any)
self.call_parent_updaters()
The automatically maintained field updates are performed here. Note that the fields to update, the function that generates their value, and the link to the parent are all maintained in a global returned by get_my_updaters() whose values are from a decorator I wrote applied to the updating functions:
def update_decorated_fields(self):
for updater_dict in self.get_my_updaters():
update_fun = getattr(self, updater_dict["function"])
update_fld = updater_dict["update_field"]
if update_fld is not None:
current_val = None
# ... brevity edit
new_val = update_fun()
setattr(self, update_fld, new_val)
print(f"Auto-updated {self.__class__.__name__}.{update_fld} using {update_fun.__qualname__} from [{current_val}] to [{new_val}]")
And in the test code example at the top of this post, where InfusateTracer linking records are created, this method is crucial to the updates that are not fully happening:
def call_parent_updaters(self):
parents = []
for updater_dict in self.get_my_updaters():
update_fun = getattr(self, updater_dict["function"])
parent_fld = updater_dict["parent_field"]
# ... brevity edit
if parent_inst is not None and parent_inst not in parents:
parents.append(parent_inst)
for parent_inst in parents:
if isinstance(parent_inst, MaintainedModel):
parent_inst.save()
elif parent_inst.__class__.__name__ == "ManyRelatedManager":
if parent_inst.count() > 0 and isinstance(
parent_inst.first(), MaintainedModel
):
for mm_parent_inst in parent_inst.all():
mm_parent_inst.save()
And here's the relevant ordered debug output:
Auto-updated Infusate.name using Infusate._name from [ti] to [ti{glucose-[2,3-13C5,4-17O1]}]
Auto-updated Infusate.name using Infusate._name from [ti{glucose-[2,3-13C5,4-17O1]}] to [ti{C16:0-[5,6-13C5,17O1];glucose-[2,3-13C5,4-17O1]}]
Name: ti{glucose-[2,3-13C5,4-17O1]}
DataRepo.models.infusate.Infusate.DoesNotExist: Infusate matching query does not exist.
Note that the output Name: ti{glucose-[2,3-13C5,4-17O1]} is incomplete (even though the debug output above it is complete: ti{C16:0-[5,6-13C5,17O1];glucose-[2,3-13C5,4-17O1]}). It contains the information resulting from the creation of the first through record:
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
But the subsequent through record created by:
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
...(while all the Auto-updated debug output is correct - and is what I expected to see), is not the final value of the Infusate record's name field (which should be composed of values gathered from 7 different records as displayed in the last Auto-updated debug output (1 Infusate record, 2 Tracer records, and 4 TracerLabel records))...
Is this due to asynchronous execution or is this because I should be using something other than setattr to save the changes? I've tested this many times and the result is always the same.
Incidentally, I lobbied our team to not even have these automatically maintained fields because of their potential to become invalid from DB changes, but the lab people like having them apparently because that's how the suppliers name the compounds, and they want to be able to copy/paste them in searches, etc).
The problem here is a misconception over how changes are applied, when they are used in the construction of the new derived field value, and when the super().save method should be called.
Here, I am creating a record:
io = Infusate.objects.create(short_name="ti")
That is related to these 2 records (also being created):
glu_t = Tracer.objects.create(compound=glu)
c16_t = Tracer.objects.create(compound=c16)
Then, those records are linked together in a through model:
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
I had thought (incorrectly) that I had to call super().save() so that when the field values are gathered together to compose the name field, those incoming changes would be included in the name.
However, the self object, is what is being used to retrieve those values. It doesn't matter that they aren't saved yet.
At this point, it's useful to include some of the gaps in the supplied code in the question. This is a portion of the Infusate model:
class Infusate(MaintainedModel):
id = models.AutoField(primary_key=True)
name = models.CharField(...)
short_name = models.CharField(...)
tracers = models.ManyToManyField(
Tracer,
through="InfusateTracer",
)
#field_updater_function(generation=0, update_field_name="name")
def _name(self):
if self.tracers is None or self.tracers.count() == 0:
return self.short_name
return (
self.short_name
+ "{"
+ ";".join(sorted(map(lambda o: o._name(), self.tracers.all())))
+ "}"
)
And this was an error I had inferred (incorrectly) to mean that the record had to have been saved before I could access the values:
ValueError: "<Infusate: >" needs to have a value for field "id" before this many-to-many relationship can be used.
when I had tried the following version of my save override:
def save(self, *args, **kwargs):
self.update_decorated_fields()
super().save(*args, **kwargs)
self.call_parent_updaters()
But what this really meant was that I had to test something else other than self.tracers is None to see if any M:M links exist. We can simply check self.id. If it's None, we can infer that self.tracers does not exist. So the answer to this question is simply to edit the save method override to:
def save(self, *args, **kwargs):
self.update_decorated_fields()
super().save(*args, **kwargs)
self.call_parent_updaters()
and edit the method that generates the value for the field update to:
#field_updater_function(generation=0, update_field_name="name")
def _name(self):
if self.id is None or self.tracers is None or self.tracers.count() == 0:
return self.short_name
return (
self.short_name
+ "{"
+ ";".join(sorted(map(lambda o: o._name(), self.tracers.all())))
+ "}"
)
I need to loop over all m2m relations of the model instance and copy them to new model instance.
source_id=request.GET.get('source_id', 1)
obj = Artist.objects.create(title='New artist')
source_obj = Artist.objects.get(id=source_id)
if source_obj.galleries.count():
obj.galleries = source_obj.galleries.all()
if source_obj.suggested_artists.count():
obj.suggested_artists = source_obj.suggested_artists.all()
Currently i am doing it like this, but i want to loop over all m2m fields and copy the related data to obj.
I want something like:
for m2m_rel in source_obj.m2m_relations:
print geattr(source_obj, m2m_rel).count()
print geattr(source_obj, m2m_rel).all()
Any suggestions?
You can access the m2m relation entries like this:
for field in source_obj._meta.many_to_many:
source = getattr(source_obj, field.attname)
for item in source.all():
# do something with item...
print repr(item)
If you're trying to clone a model intance, you can use a generic clone_objects function like the one below. The function will clone a list of objects and return a new list of cloned objects (with new ID's):
# import Python's copy library
import copy
def clone_objects(objects):
"""
Generic model object cloner function.
"""
def clone(obj):
"""Return an identical copy of the instance with a new ID."""
if not obj.pk:
raise ValueError('Instance must be saved before it can be cloned.')
duplicate = copy.copy(obj)
# Setting pk to None tricks Django into thinking this is a new object.
duplicate.pk = None
duplicate.save()
# ... but the trick loses all ManyToMany relations.
for field in obj._meta.many_to_many:
source = getattr(obj, field.attname)
destination = getattr(duplicate, field.attname)
for item in source.all():
destination.add(item)
return duplicate
if not hasattr(objects,'__iter__'):
objects = [ objects ]
objs = []
for obj in objects:
new_obj = clone(obj)
new_obj.save()
objs.append(new_obj)
return objs
The main part of the "cloning" code is from this snippet: Clone model mixin
Suppose following model class,
class Bookmark(models.Model):
owner = models.ForeignKey(UserProfile,related_name='bookmarkOwner')
parent = models.ForeignKey(UserProfile,related_name='bookmarkParent')
sitter = models.ForeignKey(UserProfile,related_name='bookmarkSitter')
How can I get sitter objects from owner Objects?
user = UserProfile.objects.get(pk=1)
UserProfile.objects.filter(bookmarkOwner=user)
returns empty tuple, and I cannot specify sitter variable.
I believe you can do something like this, if you want to avoid using a loop:
pks = some_user_profile.bookmarkOwner.values_list('sitter', flat=True)
sitters = UserProfile.objects.filter(pk__in=pks).all()
Alternatively, you might want to experiment with setting up a many-to-many field and using the through parameter. See the Django docs: https://docs.djangoproject.com/en/2.0/ref/models/fields/#manytomanyfield
you should do
objs = Bookmark.objects.filter(owner=user)
# This will return all bookmarks related to the user profile.
for obj in objs:
print obj.owner # gives owner object
print obj.parent # gives parent object
print obj.sitter # gives sitter object
If there is only one Bookmark object for a user profile (no multiple entries). Then you should use .get method instead (which return a single object).
obj = Bookmark.objects.get(owner=user)
print obj.owner
print obj.parent
print obj.sitter
I'm trying to write an internal API in my application without necessarily coupling it with the database.
class Product(models.Model):
name=models.CharField(max_length=4000)
price=models.IntegerField(default=-1)
currency=models.CharField(max_length=3, default='INR')
class Image(models.Model):
# NOTE -- Have changed the table name to products_images
width=models.IntegerField(default=-1)
height=models.IntegerField(default=-1)
url=models.URLField(max_length=1000, verify_exists=False)
product=models.ForeignKey(Product)
def create_product:
p=Product()
i=Image(height=100, widght=100, url='http://something/something')
p.image_set.add(i)
return p
Now, when I call create_product() Django throws up an error:
IntegrityError: products_images.product_id may not be NULL
However, if I call p.save() & i.save() before calling p.image_set.add(i) it works. Is there any way that I can add objects to a related object set without saving both to the DB first?
def create_product():
product_obj = Product.objects.create(name='Foobar')
image_obj = Image.objects.create(height=100, widght=100, url='http://something/something', product=product_obj)
return product_obj
Explanation:
Product object has to be created first and then assign it to the Image object because id and name here is required field.
I am wondering why wouldn't you not require to make a product entry in DB in first case? If there is any specific reason then i may suggest you some work around?
EDIT: Okay! i think i got you, you don't want to assign a product to an image object initially. How about creating a product field as null is equal to true.
product = models.ForeignKey(Product, null=True)
Now, your function becomes something like this:
def create_product():
image_obj = Image.objects.create(height=100, widght=100, url='http://something/something')
return image_obj
Hope it helps you?
I got same issue with #Saurabh Nanda
I am using Django 1.4.2. When I read in django, i see that
# file django/db/models/fields/related.py
def get_query_set(self):
try:
return self.instance._prefetched_objects_cache[rel_field.related_query_name()]
except (AttributeError, KeyError):
db = self._db or router.db_for_read(self.model, instance=self.instance)
return super(RelatedManager,self).get_query_set().using(db).filter(**self.core_filters)
# file django/db/models/query.py
qs = getattr(obj, attname).all()
qs._result_cache = vals
# We don't want the individual qs doing prefetch_related now, since we
# have merged this into the current work.
qs._prefetch_done = True
obj._prefetched_objects_cache[cache_name] = qs
That 's make sese, we only need to set property _prefetched_objects_cache for the object.
p = Product()
image_cached = []
for i in xrange(100):
image=Image(height=100, widght=100, url='http://something/something')
image_cached.append(image)
qs = p.images.all()
qs._result_cache = image_cached
qs._prefetch_done = True
p._prefetched_objects_cache = {'images': qs}
Your problem is that the id isn't set by django, but by the database (it's represented in the database by an auto-incremented field), so until it's saved there's no id. More about this in the documentation.
I can think of three possible solutions:
Set a different field of your Image model as the primary key (documented here).
Set a different field of your Production model as the foreign key (documented here).
Use django's database transactions API (documented here).
I have two related models (one to many) in my django app and When I do something like this
ObjBlog = Blog()
objBlog.name = 'test blog'
objEntry1 = Entry()
objEntry1.title = 'Entry one'
objEntry2 = Entry()
objEntry2.title = 'Entry Two'
objBlog.entry_set.add(objEntry1)
objBlog.entry_set.add(objEntry2)
I get an error which says "null value in column and it violates the foreign key not null constraint".
None of my model objects have been saved. Do I have to save the "objBlog" before I could set the entries? I was hoping I could call the save method on objBlog to save it all.
NOTE: I am not creating a blog engine and this is just an example.
I would guess that one of your models has a Foreign Key field which is not nullable.
When you do objBlog.entry_set.add(objEntry1) django calls save() on each object.
This is how the add method looks like:
def add(self, *objs):
for obj in objs:
if not isinstance(obj, self.model):
raise TypeError("'%s' instance expected" % self.model._meta.object_name)
setattr(obj, rel_field.name, instance)
obj.save()
add.alters_data = True