I have a model that has a many to many relationship to another model.
I am trying to update the many to many relationship on save, but nothing is being added.
Creating a new Flight via the Python Interpreter, saving it, and then running the loop I have in the 'save' method adds the correct lanes to the many to many relationship.
What am I missing in the overridden save method?
class Flight(models.Model):
number_of_lanes = models.PositiveSmallIntegerField()
start_time = models.TimeField()
lanes = models.ManyToManyField(Lane, blank=True)
tournament = models.ForeignKey('Tournament')
def __unicode__(self):
return u'Lanes: %s | Start: %s' % (self.number_of_lanes, self.start_time)
def save(self, *args, **kwargs):
super(Flight, self).save(*args, **kwargs)
for i in range(1, self.number_of_lanes+1):
lane = Lane.objects.get(id=i)
self.lanes.add(lane)
Here is the Console snippet from where I tested it:
>>> flight = Flight()
>>> flight.number_of_lanes=5
>>> flight.start_time='8:30'
>>> flight.tournament=t
>>> flight.save()
>>> flight.lanes.all()
[<Lane: 1>, <Lane: 2>, <Lane: 3>, <Lane: 4>, <Lane: 5>]
Edit:
Brief update on where I am on this.
The save method works within the console. The first time I tested it, I forgot to reload the Django shell. The many to many relationship is still not being created when adding from the admin page.
If the overridden save method works within the shell, shouldn't it work on the Django admin page?
You should take a look at the following article. It basically describes that when you save a model through the admin forms, it isn't an atomic transaction.
The main object is saved first, then the M2M is cleared and the new
values set to whatever came out of the form. So if you are in the
save() of the main object you are in a window of opportunity where the
M2M hasn't been updated yet. In fact, if you try to do something to
the M2M, the change will get wiped out by the clear().
Related
I have an object that has a m2m relation, and I would like to populate it after saving.
The problem is that the signal is triggered, but the command add doesn't work. I did try the same steps using python shell, and it worked fine.
class Event(models.Model):
name = models.CharField(max_lenght=40)
location = models.ManyToManyField('Location')
class Location(models.Model):
address = models.CharField(max_lenght=60)
#receiver(post_save, sender=Event)
def populate_location(sender, instance, **kwargs):
instance.locations.add(*Locations.objects.all())
Any hint?
If you want to add all the objects you can use set() instead of add, you can have a look on documentation - https://docs.djangoproject.com/en/2.1/ref/models/relations/#django.db.models.fields.related.RelatedManager.set
One more suggestion, if you are using add then try to print instance.locations just after executing - instance.locations.add(*Locations.objects.all()) and post the result.
I have found the solution. I forgot to mention that I was trying to save from admin, and it seems that it was a crutial detail, sorry about this.
https://timonweb.com/posts/many-to-many-field-save-method-and-the-django-admin/
I'm trying to track m2m change with signal to create activity history, I'm using django activity stream
I have tried to use pre_save signals and compare the origin and actual states of the field, but for a reason I can't understand my field is at None even when it contains information, here is the code
#receiver(pre_save, sender=Artwork)
def artwork_update_handler(sender, instance, **kwargs):
orig = Artwork.objects.get(pk=instance.pk)
print (orig.collectors)
print (instance.collectors)
if orig.collectors != instance.collectors:
print ("collectors diff")
I have also tried to use m2m_changed signals, but signals are sent even when updating an other field in the models and I can't know which fields are update
It's not that easy to track m2m changes. I did have similar requirement before, what I ended up doing is using django simple history package. It's a package that simply track all changes on model objects(create, update, delete). However, m2m fields do not explicitly exist for normal cases, so I added a through model just for history tracking. It might be overkill depend on how bad do you want this feature, but it definitely worth go give it a try.
m2m fields send 2 signals on save: 'pre_add', 'post_add', 'pre_remove', 'post_remove'. There is no pre_save.
Therefore it will look something like this:
#receiver(m2m_changed, sender=Artwork.the_m2m_field.through)
def artwork_update_handler(sender, instance, action, model, pk_set, **kwargs):
if action == 'pre_save':
orig = Artwork.objects.get(pk=instance.pk)
print (orig.collectors)
print (instance.collectors)
if orig.collectors != instance.collectors:
print ("collectors diff")
I have the following two models (just for a test):
class IdGeneratorModel(models.Model):
table = models.CharField(primary_key=True, unique=True,
null=False, max_length=32)
last_created_id = models.BigIntegerField(default=0, null=False,
unique=False)
#staticmethod
def get_id_for_table(table: str) -> int:
try:
last_id_set = IdGeneratorModel.objects.get(table=table)
new_id = last_id_set.last_created_id + 1
last_id_set.last_created_id = new_id
last_id_set.save()
return new_id
except IdGeneratorModel.DoesNotExist:
np = IdGeneratorModel()
np.table = table
np.save()
return IdGeneratorModel.get_id_for_table(table)
class TestDataModel(models.Model):
class Generator:
#staticmethod
def get_id():
return IdGeneratorModel.get_id_for_table('TestDataModel')
id = models.BigIntegerField(null=False, primary_key=True,
editable=False, auto_created=True,
default=Generator.get_id)
data = models.CharField(max_length=16)
Now I use the normal Django Admin site to create a new Test Data Set element. What I expected (and maybe I'm wrong here) is, that the method Generator.get_id() is called exactly one time when saving the new dataset to the database. But what really happens is, that the Generator.get_id() method is called three times:
First time when I click the "add a Test Data Set" button in the admin area
A second time shortly after that (no extra interaction from the user's side)
And a third time when finally saving the new data set
The first time could be OK: This would be the value pre-filled in a form field. Since the primary key field is not displayed in my form, this may be an unnecessary call.
The third time is also clear: It's done before saving. When it's really needed.
The code above is only an example and it is a test for me. In the real project I have to ask a remote system for an ID instead from another table model. But whenever I query that system, the delivered ID gets locked there - like the get_id_for_table() method counts up.
I'm sure there are better ways to get an ID from a method only when really needed - the method should be called exactly one time - when inserting the new dataset. Any idea how to achieve that?
Forgot the version: It's Django 1.8.5 on Python 3.4.
This is not an answer to your question, but could be a solution to your problem
I believe this issue is very complicated. Especially because you want a transaction that spans a webservice call and a database insert... What I would use in this case: generate a uuid locally. This value is practially guaranteed to be unique in the 4d world (time + location) and use that as id. Later, when the save is done, sync with your remote services.
Hello,
I have bound a ModelForm to one of my model that contains a ForeignKey to another model everything driven by a CreateView. What I want to achieve is to create the model object corresponding to the foreign key if it doesn't exist before the form is overall validated and the final object created in database.
Below the models I use:
class UmsAlerting(models.Model):
alert_id = models.IntegerField(primary_key=True, editable=False)
appli = models.ForeignKey('UmsApplication')
env = models.ForeignKey('UmsEnvironment')
contact = models.ForeignKey('UmsContacts')
custom_rule = models.ForeignKey('UmsCustomRules', null=True, blank=True)
class UmsApplication(models.Model):
appli_id = models.IntegerField(primary_key=True)
trigram_ums = models.CharField(max_length=4L)
class UmsContacts(models.Model):
contact_id = models.IntegerField(primary_key=True)
mail_addr = models.CharField(max_length=100L)
class UmsEnvironment(models.Model):
env_id = models.IntegerField(primary_key=True)
env_name = models.CharField(max_length=5L)
The model bound to the form is UmsAlerting. The model object I want to create if it doesn't exist is UmsContacts. I managed to use the field's clean method in my ModelForm of the contact field and use the get_or_create method like below:
def clean_contact(self):
data = self.cleaned_data['contact']
c, _ = UmsContacts.objects.get_or_create(mail_addr=data)
return c
It perfectly works when the contact is already in the database but when it needs to be created my form return a ValidationError on the contact field saying "This field cannot be null". If I submit the same form a second time without changing anything the UmsAlerting object is well created with no validation error.
My guess is that, for a reason I don't get, when get_or_create is used to create a UmsContacts object it cannot be used to create the new UmsAlerting object. So in clean_contact method the get is working and returns the UmsContacts object but the create part doesn't. It'd be like the UmsContacts object is saved when the whole form is validated but not before as I'd want it to.
Anyone could help me find out what is the problem ? Is using the clean method not the best idea ? Is there another strategy to use to take around this problem ?
Thanks in advance for your help.
It's probably because the object you are creating expects value for contact_id. If you use contact_id field for just setting object id -then you do not have to create it at all. Django takes care of Id's automatically.
Also. field clean method should return cleaned data not object. That creates whole lot more problems on its own.
I have a couple of models in django which are connected many-to-many. I want to create instances of these models in memory, present them to the user (via custom method-calls inside the view-templates) and if the user is satisfied, save them to the database.
However, if I try to do anything on the model-instances (call rendering methods, e.g.), I get an error message that says that I have to save the instances first. The documentation says that this is because the models are in a many-to-many relationship.
How do I present objects to the user and allowing him/her to save or discard them without cluttering my database?
(I guess I could turn off transactions-handling and do them myself throughout the whole project, but this sounds like a potentially error-prone measure...)
Thx!
I would add a field which indicates whether the objects are "draft" or "live". That way they are persisted across requests, sessions, etc. and django stops complaining.
You can then filter your objects to only show "live" objects in public views and only show "draft" objects to the user that created them. This can also be extended to allow "archived" objects (or any other state that makes sense).
I think that using django forms may be the answer, as outlined in this documentation (search for m2m...).
Edited to add some explanation for other people who might have the same problem:
say you have a model like this:
from django.db import models
from django.forms import ModelForm
class Foo(models.Model):
name = models.CharField(max_length = 30)
class Bar(models.Model):
foos = models.ManyToManyField(Foo)
def __unicode__(self):
return " ".join([x.name for x in foos])
then you cannot call unicode() on an unsaved Bar object. If you do want to print things out before they will be saved, you have to do this:
class BarForm(ModelForm):
class Meta:
model = Bar
def example():
f1 = Foo(name = 'sue')
f1.save()
f2 = foo(name = 'wendy')
f2.save()
bf = BarForm({'foos' : [f1.id, f2.id]})
b = bf.save(commit = false)
# unfortunately, unicode(b) doesn't work before it is saved properly,
# so we need to do it this way:
if(not bf.is_valid()):
print bf.errors
else:
for (key, value) in bf.cleaned_data.items():
print key + " => " + str(value)
So, in this case, you have to have saved Foo objects (which you might validate before saving those, using their own form), and before saving the models with many to many keys, you can validate those as well. All without the need to save data too early and mess up the database or dealing with transactions...
Very late answer, but wagtail's team has made a separate Django extension called django-modelcluster. It's what powers their CMS's draft previews.
It allows you to do something like this (from their README):
from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalKey
class Band(ClusterableModel):
name = models.CharField(max_length=255)
class BandMember(models.Model):
band = ParentalKey('Band', related_name='members')
name = models.CharField(max_length=255)
Then the models can be used like so:
beatles = Band(name='The Beatles')
beatles.members = [
BandMember(name='John Lennon'),
BandMember(name='Paul McCartney'),
]
Here, ParentalKey is the replacement for Django's ForeignKey. Similarly, they have ParentalManyToManyField to replace Django's ManyToManyField.