2

I have multiple models which relate back to a single model. On save of these models I have overridden save to retrieve the id of the main model so as to place files on the OS in a directory keyed by the main model's pk.

For example, take a Building with many rooms. Any images of rooms will be saved in the directory keyed by the building's id (no subdirectory for rooms).

My overridden save methods work fine as long as the Building exists when the room is saved. If however the Building is not yet saved, and I am adding a room to the building via the django admin, the image remains in an upload directory, as no pk for the building exists yet.

I initially tried overriding save on Building and moving any Room images to a newly created Building directory (again keyed off building's pk). Despite super(Building, self).save(*args, **kwargs) first the Building's id was not set.

I then decided post_save signal was probably cleaner anyway and did such. Unfortunately, the id doesn't seem to exist in the post save either. I can attempt to print the ID and see no value when post save is triggered until I have saved the model for a second time.

Could someone point me in a direction that might explain why id is not getting set as would be expected output as accepted in this other SO answer?

Thanks.

Edit:

Here is some code as requested in a comment. I'm including a bit more here as I had simplified the initial question. Here there are 3 levels, a listing with buildings with rooms. The listing is what I attempt to simply print via the print kwargs['instance'] line. At the bottom I have included the output after two back to back saves. Notice the complete lack of an instance in existence after the first save. These were literally back to back with no actions between. References to things like Building_Room are through tables. RoomImage, BuildingImage, and ListingImage are all alike aside from data fields and I have therefore only included one.

class Listing(models.Model):
    ...
    buildings = models.ManyToManyField('Building', null=True, blank=True, through = 'Building_Listing')
    addresses = models.ManyToManyField(Address, null=True, blank=True)
    def __unicode__(self):
        return '  &  '.join([a.__unicode__() for a in self.addresses.all()])

class Building(models.Model):
    ...
    rooms = models.ManyToManyField('Room', null=True, through="Building_Room")
    def __unicode__(self):
        return self.description

class Room(models.Model):
    ...
    def __unicode__(self):
        return str(self.room_type)

class RoomImage(models.Model):
    room = models.ForeignKey(Room)
    room_photo = FileBrowseField("Image", max_length=200, blank=True, null=True)

    def save(self, *args, **kwargs):
        try:
            listing = Building_Listing.objects.get(building=Building_Room.objects.get(room=self.room).building).listing
            self.room_photo = moveFileBeforeSave(listing, self.room_photo)
        except Building_Listing.DoesNotExist:
            pass
        except Building_Room.DoesNotExist:
            pass
        super(RoomImage, self).save(*args, **kwargs)

@receiver(post_save, sender=Listing, weak=False)
def save_images_on_listing_create(sender, **kwargs):
    #if kwargs['created']:
    listing = kwargs['instance']
    print kwargs['instance']
    listing_image_list = ListingImage.objects.filter(listing = listing)
    listing_buildings = Building_Listing.objects.filter(listing = listing).values_list('building', flat=True)
    building_image_list = BuildingImage.objects.filter(building__in = listing_buildings)
    building_rooms = Building_Room.objects.filter(building__in = listing_buildings).values_list('room', flat=True)
    room_image_list = RoomImage.objects.filter(room__in = building_rooms)
    for image in listing_image_list:
        image.save()
    for image in building_image_list:
        image.save()
    for image in room_image_list:
        image.save()

@receiver(post_save, sender=Building, weak=False)
def save_images_in_building_create(sender, **kwargs):
    #if kwargs['created']:
    print str(kwargs['instance'])+" : building save trigger"
    building = kwargs['instance']
    building_image_list = BuildingImage.objects.filter(building = building)
    building_rooms = Building_Room.objects.filter(building = building).values_list('room', flat=True)
    room_image_list = RoomImage.objects.filter(room__in = building_rooms)
    for image in building_image_list:
        image.save()
    for image in room_image_list:
        image.save()

Some output:

[30/Oct/2011 19:52:05] "POST /admin/mls/building/add/?_popup=1 HTTP/1.1" 200 97
# This is the print of the instance kwarg after the first save (ie nothing)
[30/Oct/2011 19:52:10] "POST /admin/mls/listing/add/ HTTP/1.1" 302 0
[30/Oct/2011 19:52:10] "GET /admin/mls/listing/8/ HTTP/1.1" 200 72495
[30/Oct/2011 19:52:10] "GET /admin/jsi18n/ HTTP/1.1" 200 2158
1 Ben Blvd sometown, MN #this is the print of the instance kwarg after the second save
[30/Oct/2011 19:52:12] "POST /admin/mls/listing/8/ HTTP/1.1" 302 0
[30/Oct/2011 19:52:13] "GET /admin/mls/listing/8/ HTTP/1.1" 200 72497
[30/Oct/2011 19:52:13] "GET /admin/jsi18n/ HTTP/1.1" 200 2158
Community
  • 1
  • 1
wilbbe01
  • 1,931
  • 1
  • 24
  • 38
  • Are you sure the post_save signal is attached to the saving of the Buildling, and not the Room? Can you post your signals and where you register the signals? If the signal fires after the Building is saved then there is no reason why you shouldn't be able to see the id (unless you saved with commit=False in which case I"m not sure whether the signal fires at all?) – Timmy O'Mahony Oct 31 '11 at 00:48
  • I am pretty sure the post save is attached to the correct model as I have sender set, and also get the expected output after saving a second time immediately after the first. Signals/registering decorators and related model code has been included in an edit. No commit=False. As referenced by the included output the signals are firing. Thanks for your help, my mind is boggled...hopefully I'm doing something obvious. – wilbbe01 Oct 31 '11 at 01:21

2 Answers2

2

Ok, so the problem looks like it is because of the manytomany relationship you are using. Have a look at these post:

save with many-to-many relationship in django problem

Issue with ManyToMany Relationships not updating inmediatly after save

I'd consider refactoring you code to change the relationship between a building and it's rooms. At the moment you are saying 'there are many rooms and a building can associate itself with a number of those rooms'. Furthermore, two buildings can be associated with the same room. This doesn't really make sense. Really a room should be associated with one and only one buildling, i.e.

class Building(models.Model):
    name = models.CharField(...)
    ...

class Room(models.Model):
    building = models.ForeignKey(Building, unique=True)
    ....

this means that any room can only be linked to one particular building.

Community
  • 1
  • 1
Timmy O'Mahony
  • 53,000
  • 18
  • 155
  • 177
  • Thanks. Completely with you on the Foreignkey instead of manytomany idea. I think what has pushed us to using manytomany is our use of the through/manytomany models. Right now we have Inlines for the through tables (ie Building_Room). We're doing it this way for purposes of solving the lack of a nested inline. Clicking our "edit" on a building_inline brings up the building admin display for that referenced building. For now until we can find out a better solution more along your recommendations we might try post_save on the through tables themselves. Trying that soon. Thanks for your help. – wilbbe01 Oct 31 '11 at 03:43
  • I feel your plight! You should be careful to not (like I have) fall into the trap of designing your DB and business logic based on the functionality of the django-admin - these limitations are usually due to the frontend code and bending your backend design to to make life easier can sometimes cause problems. – Timmy O'Mahony Oct 31 '11 at 09:50
  • Yeah, exactly what I have been thinking. It was a bit backwards of that here though (poorly designed models --> admin built on those --> unable to change easily now). Initially we thought we did want m2m for some reason (no clue why), then we built the admin. So our admin is relying on our incorrectly designed database too much to fix it at this time. Thanks again for your help and suggestions. – wilbbe01 Oct 31 '11 at 14:50
0

First, pastylegs is right and his answer is better. But if for some reason (like me right now) are unable to change code due to whatever reason and find yourself in the post_save model instance nonexistent problems like we have found ourselves in the past few days the following idea may be of assistance.

In our case the manytomany through table was sufficient enough for the post_save. By attaching the post save signal to the through table itself we were basically able to catch all cases we needed to do post_saving for, and being as the ids to the two connected tables for the manytomany relationship existed in the through table this was sufficient to get the job done. So if you find yourself here for similar reasons, can you attach the post_save to a through table instead?

Again, pastylegs is right, but if you cannot do the same for whatever reason I hope this is helpful.

wilbbe01
  • 1,931
  • 1
  • 24
  • 38