I'm completely stumped as to why this isn't working:
flight = Flight.objects.get(pk=flight_id)
print "old", flight.route.pk ## `route` is a ForeignKey field to model Route
print "new", new_route.pk
flight.route=new_route # new_route is a newly created Route object
flight.save()
print "db", Flight.objects.get(pk=flight_id).route.pk
this is the output:
old 4800
new 7617
db 4800
Is there some special way I need to call save() on the flight to get it to actually save?
edit: my models look like this:
class Flight(models.Model):
route = models.ForeignKey(Route, blank=True, null=True, related_name="flight")
class Route(models.model):
# a bunch of CharFields and IntegerFields
Has the new_route been saved? Assuming pk would not return a result, but unable to test.
Otherwise see http://code.djangoproject.com/ticket/8892
OK I just figured it out, I recently moved my custom save function from the bottom of the class definition to the top, and I forgot the last line that calls super(Flight, self).save(*args, **kwargs)
Related
class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
purchase_order_number = models.CharField(unique=True)
vendor = models.ForeignKey(Vendor)
i am creating Purchase Order(po) table. when po created i have to update purchase_order_number as "PO0"+purchase_order_id ex PO0123 (123 is Primary key). so i am using def save in models to accomplish this
def save(self):
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
return super(PurchaseOrder, self).save()
It is working fine with single creation but when i try to create bulk of data using locust(Testing tool) its giving an error duplicate entry for PurchseOrdernumber Can we modify field value in models itself some thing like this
purchase_order_number = models.CharField(unique=True,default=("PO"+self.purchase_order_id )
To be honest, I don't think it should work when you create multiple instances. Because as I can see from the code:
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
Here purchase_order_id will be None when you are creating new instance. Also, until you call super(PurchaseOrder, self).save(), it will not generate purchase_order_id, meaning purchase_order_number will be empty.
So, what I would recommend is to not store this information in DB. Its basically the same as purchase_order_id with PO in front of it. Instead you can use a property method to get the same value. Like this:
class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
# need to remove `purchase_order_number = models.CharField(unique=True)`
...
#property
def purchase_order_number(self):
return "PO{}".format(self.purchase_order_id)
So, you can also see the purchase_order_number like this:
p = PurchaseOrder.objects.first()
p.purchase_order_number
Downside of this solution is that, you can't make any query on the property field. But I don't think it would be necessary anyway, because you can do the same query for the purchase_order_id, ie PurchaseOrder.objects.filter(purchase_order_id=1).
In my project, i have 2 models:
class Product(models.Model):
name = models.CharField(max_length=200)
class Material(models.Model):
name = models.CharField(max_length=200)
product = models.ForeignKey(Product)
Now, I want to make a copy of Product and keep all of the assigned materials.
This is what I tried:
new_product = Product.object.get(pk='someexistingpk')
new_product.pk = None
new_product.name += ' (Copy)'
new_product.save()
Another variant I tried:
new_product = deepcopy(Product.object.get(pk='someexistingpk'))
new_product.pk = None
new_product.name += ' (Copy)'
new_product.save()
But in both cases, the resulting model_set is empty. None of the attached items are kept.
new_product.material_set.all()
<QuerySet []>
How can I resolve this? Ideally without iterating over every item in the original material_set.
Given that ForeignKeys, can not have multiple assignments, it only makes sense that they are not copied over, as that would break the original object.
So, when copying over the Product, one should also do the same for the related objects. Like this:
new_prod = deepcopy(prod)
new_prod.pk = None
new_prod.save()
for mat in prod.material_set.all():
new_mat = deepcopy(mat)
new_mat.pk = None
new_mat.product = new_prod
new_mat.save()
Now, it results in a nice Queryset with all of the material objects attached, as expected.
new_prod.material_set.all()
<QuerySet [<Material: 1.01 Katoen cats and dogs>, <Material: 1.0 Hour-cost>, <Material: 2.0 lint>, <Material: 1.0 Katoen cats and dogs>]>
From my understanding you are trying to duplicate a model. The way I approached this problem in my project was using dynamic models and inheritance. If you use inheritance, all the fields are going to be automatically copied to the new model.
from MyApp.models import Product
class Meta:
pass
setattr(Meta, 'app_label', 'MyApp')
attrs = {'__module__': module, 'Meta': Meta}
model = type('ProductCopy', (Product,), attrs)
So here the new model that is created is called ProductCopy and will have a table on your Django Admin page.
To learn more about dynamic models you can take a look at this documentation.
My Content model has a many-to-many relationship to the Tag model. When I save a Content object, I want to add the relationships dynamically. Im doing this the following way.
def tag_content(obj):
obj.tags.add([1,2,3])
obj.is_tagged = True
obj.save()
return obj
class Tag(models.Model):
name = models.CharField(max_length=255)
class Content(models.Model):
title = models.CharField(max_length=255)
is_tagged = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag, blank=True)
def save(self, *args, **kwargs):
super(Content, self).save(*args, **kwargs)
#receiver(post_save, sender = Content)
def update_m2m_relationships_on_save(sender, **kwargs):
if not kwargs['instance'].is_tagged:
tag_content(kwargs['instance'])
Basically, when a Content object is saved, the receiver is used to call a post_save method which in turn calls the tag_content method to add the m2m relationships. However, I get this error:
TypeError
unhashable type: 'list'
It specifically references the .add() function. Any idea why I am getting this error? Any help is appreciated. Also, do note that I have Tag objects with ids = 1, 2, and 3 in database.
EDIT
Alright, I changed my tag_content to something like this:
def tag_content(obj):
for tag in Tag.objects.all():
print tag
obj.tags.add(tag)
This is because the add() method takes in object instances, not ids. However, it still doesn't work :/ I get no error, but the relationships are simply not established.
This is especially weird since the print tag command works and prints out the tags. In other words, the function is being called. Any help please? Btw, I am running Django 1.9.8.
You cannot pass a list to add(), which is why you get the error. You either need to add one item at a time or expand the list into a series of arguments, e.g:
obj.tags.add(*[1,2,3]) # The * expands the list into the function arguments
This will still cause an error because you cannot pass IDs to add() - you have to pass Tag objects to it. So something like this would work:
# Get a list of Tag objects
tags_to_add = [Tag.objects.get(id=j) for j in [1, 2, 3]]
# Now pass this to the add() function:
obj.tags.add(*tags_to_add)
According to the add docs you need actual models to be passed as args.
If you really want a list of models you need to unpack it, but you might as well pass the objs as arguments directly in this case.
ForeignKeys on django have the attribute on_delete to specify the behavior when the referenced object is deleted. Is there any way to get something similar for ManyToManyField?
Suppose I have the following model
class House(models.Model):
owners = models.ManyToManyField(Person)
The default behavior is to cascade, so if I delete a person that happens to own a house, it just vanishes from owners (that is, obviously, it no longer owns any houses). What I'd like to have is that if a person is an owner, it can not be deleted. That is, I want on_delete=models.PROTECT. Is this possible?
I know internally ManyToManyField is translated to another model with two ForeignKeys (in this case one to house and one to person), so it should be possible to achieve this. Any ideas how to? I'd like to avoid setting the through attribute to a new model, because this would result in a new table (I'd like to keep the old one).
Edit: I've tracked where django creates the appropriate m2m model:
def create_many_to_many_intermediary_model(field, klass):
from django.db import models
# ...
# Construct and return the new class.
return type(name, (models.Model,), {
'Meta': meta,
'__module__': klass.__module__,
from_: models.ForeignKey(klass,
related_name='%s+' % name,
db_tablespace=field.db_tablespace),
to: models.ForeignKey(to_model,
related_name='%s+' % name,
db_tablespace=field.db_tablespace)
})
The relevant line is
to: models.ForeignKey(to_model,
related_name='%s+' % name,
db_tablespace=field.db_tablespace)
I'd like it to be
to: models.ForeignKey(to_model,
related_name='%s+' % name,
db_tablespace=field.db_tablespace,
on_delete=models.PROTECT)
Any way to do this other than monkey patching the whole thing and creating a new class for ManyToManyField?
I think the smartest thing to do is use an explicit through table. I realise that you've stated you would prefer not to "because this would result in a new table (I'd like to keep the old one)."
I suspect your concern is over losing the data you have. If you're using South, you can easily "convert" your existing, automatic intermediate table to an explicit one OR, you can create a completely new one, then migrate your existing data to the new table before dropping your old one.
Both of these methods are explained here: Adding a "through" table to django field and migrating with South?
Considering the change you'd like to make to its definition, I'd probably go with the option of creating a new table, then migrating your data over. Test to make sure all your data is still there (and that your change does what you want), then drop the old intermediate table.
Considering that these tables will both only hold 3 integers per row, this is likely to be a very manageable exercise even if you have a lot of houses and owners.
If I understand you want, this is similar to what I need some time ago.
Your problem: you need to protect a record that is used in another table from accidental deletion.
I solved it from this way (tested on Django 2 and Django 3).
Imagine, you have:
TABLE1 and TABLE 2, and they are under M2M relationship where TABLE1 has ManyToManyField.
I put the main keys to you understand at uppercase, you will need to adjust to what you want.
Look at views.py that use the exists() method and rise the exception are crucial.
models.py
class TABLE1(models.Model):
FIELD_M2M = models.ManyToManyField(
TABLE2,
blank=False,
related_name='FIELD_M2M',
)
#put here your code
models.py
class TABLE2(models.Model):
#Put here your code
views.py
# Delete
#login_required
def delete(request, pk=None):
try: # Delete register selected
if TABLE1.objects.filter(FIELD_M2M=pk).exists():
raise IntegrityError
register_to_delete = get_object_or_404(TABLE2, pk=pk)
# register_to_delete.register_to_delete.clear() // Uncomment this, if you need broken relationship M2M before delete
register_to_delete.delete()
except IntegrityError:
message = "The register couldn't be deleted!"
messages.info(request, message)
That is a ugly solution, but it works.
Posting my own solution as requested by #Andrew Fount. Quite an ugly hack just to change a single line.
from django.db.models import ManyToManyField
from django.db.models.fields.related import ReverseManyRelatedObjectsDescriptor, add_lazy_relation, create_many_to_many_intermediary_model, RECURSIVE_RELATIONSHIP_CONSTANT
from django.utils import six
from django.utils.functional import curry
def create_many_to_many_protected_intermediary_model(field, klass):
from django.db import models
managed = True
if isinstance(field.rel.to, six.string_types) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
to_model = field.rel.to
to = to_model.split('.')[-1]
def set_managed(field, model, cls):
field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
add_lazy_relation(klass, field, to_model, set_managed)
elif isinstance(field.rel.to, six.string_types):
to = klass._meta.object_name
to_model = klass
managed = klass._meta.managed
else:
to = field.rel.to._meta.object_name
to_model = field.rel.to
managed = klass._meta.managed or to_model._meta.managed
name = '%s_%s' % (klass._meta.object_name, field.name)
if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to == klass._meta.object_name:
from_ = 'from_%s' % to.lower()
to = 'to_%s' % to.lower()
else:
from_ = klass._meta.object_name.lower()
to = to.lower()
meta = type('Meta', (object,), {
'db_table': field._get_m2m_db_table(klass._meta),
'managed': managed,
'auto_created': klass,
'app_label': klass._meta.app_label,
'db_tablespace': klass._meta.db_tablespace,
'unique_together': (from_, to),
'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to},
'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to},
})
# Construct and return the new class.
return type(name, (models.Model,), {
'Meta': meta,
'__module__': klass.__module__,
from_: models.ForeignKey(klass, related_name='%s+' % name, db_tablespace=field.db_tablespace),
### THIS IS THE ONLY LINE CHANGED
to: models.ForeignKey(to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, on_delete=models.PROTECT)
### END OF THIS IS THE ONLY LINE CHANGED
})
class ManyToManyProtectedField(ManyToManyField):
def contribute_to_class(self, cls, name):
# To support multiple relations to self, it's useful to have a non-None
# related name on symmetrical relations for internal reasons. The
# concept doesn't make a lot of sense externally ("you want me to
# specify *what* on my non-reversible relation?!"), so we set it up
# automatically. The funky name reduces the chance of an accidental
# clash.
if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name):
self.rel.related_name = "%s_rel_+" % name
super(ManyToManyField, self).contribute_to_class(cls, name)
# The intermediate m2m model is not auto created if:
# 1) There is a manually specified intermediate, or
# 2) The class owning the m2m field is abstract.
# 3) The class owning the m2m field has been swapped out.
if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped:
self.rel.through = create_many_to_many_protected_intermediary_model(self, cls)
# Add the descriptor for the m2m relation
setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
# Set up the accessor for the m2m table name for the relation
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
# Populate some necessary rel arguments so that cross-app relations
# work correctly.
if isinstance(self.rel.through, six.string_types):
def resolve_through_model(field, model, cls):
field.rel.through = model
add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
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).