Django - access ManyToManyField right after object was saved - python

I need to notify users by email, when MyModel object is created. I need to let them know all attributes of this object including ManyToManyFields.
class MyModel(models.Model):
charfield = CharField(...)
manytomany = ManyToManyField('AnotherModel'....)
def to_email(self):
return self.charfield + '\n' + ','.join(self.manytomany.all())
def notify_users(self):
send_mail_to_all_users(message=self.to_email())
The first thing I tried was to override save function:
def save(self, **kwargs):
created = not bool(self.pk)
super(Dopyt, self).save(**kwargs)
if created:
self.notify_users()
Which doesn't work (manytomany appears to be empty QuerySet) probably because transaction haven't been commited yet.
So I tried post_save signal with same result - empty QuerySet.
I can't use m2mchanged signal because:
manytomany can be None
I need to notify users only if object was created, not when it's modified
Do you know how to solve this? Is there some elegant way?

Related

Django model update or create object with unique constraint

There is a model:
class Proxy(models.Model):
host = models.CharField(max_length=100,)
port = models.CharField(max_length=10,)
login = models.CharField(max_length=100,)
password = models.CharField(max_length=100,)
class Meta:
unique_together = ("host", "port")
I added batch of proxies in admin interface, and one of them is 0.0.0.0:0000, login=123, password=123.
Then I add another batch of proxy, and one of them is the same 0.0.0.0:0000, but with new login=234 and password=234.
Is any possibility to override save method of model to get behaviour like "insert ... on conflict (host, port) do update set login=login, password=password".
Django 2, db - Postgres.
Finally I found answer by myself. If anyone need it here is it:
(1) Deactivate validate_unique on unique fields in model:
def validate_unique(self, exclude=None):
super().validate_unique(exclude='host')
This check is called before save_model() or save() actions. So any other changes will be useless.
(2) Override save method:
def save(self, *args, **kwargs):
proxy = Proxy.objects.get(host=self.host, port=self.port)
if proxy:
self.id = proxy.id
super().save(*args, **kwargs, update_fields=["login", "password"])
else:
super().save(*args, **kwargs)
No update_or_create() or something similar approaches does not work here because they lead to infinite recursion. Just update or save method (even with force_update option) does not work too, because current object have no id yet. So we need to get that id if such object exists and update it or just create new object.

How to save related model instances before the model instance in django?

How to save the related model instances before the instance model.
This is necessary because I want to preprocess the related model's instance field under model instance save method.
I am working on Django project, and I am in a situation, that I need to run some function, after all the related models of instance get saved in the database.
Let say I have a model
models.py
from . import signals
class Video(models.Model):
"""Video model"""
title = models.CharField(
max_length=255,
)
keywords = models.ManyToManyField(
KeyWord,
verbose_name=_("Keywords")
)
When the new instance of video model is created.
I need to
1. All the related models get saved first.
a. If the related models are empty return empty or None
2. and then Save this video instance.
I tried to do it using post_save signals, but couldn't succeed as there is no guarantee that related models get saved first that the model.
from django.db.models.signals import post_save, pre_delete, m2m_changed
from django.dispatch import receiver
from .models import Video
#receiver(m2m_changed, sender=Video)
#receiver(post_save, sender=Video)
def index_or_update_video(sender, instance, **kwargs):
"""Update or create an instance to search server."""
# TODO: use logging system
# Grab the id
print("Id is", instance.id)
# Keywords is empty as keyword instance is saved later than this instace.
keywords = [keyword.keyword for keyword in instance.keywords.all()]
print(keywords) # [] empty no keywords
instance.index()
#receiver(pre_delete, sender=Video)
def delete_video(sender, instance, **kwargs):
print("Delete index object")
instance.delete()
Update:
Can be implemented by grabbing the post_save signals and wait unitls
its related models get saved in db, when the related_models get saved
start serialization process and create flat json file along with the models fields and its related instance so, the flat json file can index
into elastic search server.
And the question aries, how much time should we wait in signal handler method? and how to know all instance related fields got saved in db.
class Video(models.Model):
def save(self, *args, **kwargs):
# 1. Make sure all of its related items are saved in db
# 2. Now save this instance in db.
# 3. If the model has been saved. Serialize its value,
# 4. Serailize its related models fields
# 5. Save all the serialized data into index server
# The advantage of using this is the data are indexed in real
# time to index server.
# I tired to to implement this logic using signals, in case of
# signals, when the instance get saved, its related models are
# not instantly available in the databse.
# Other solution could be, grab the `post_save` signals, wait(delay
# the serialization process) and start the serialization of
# instance model and it's related to convert the data to flat json
# file so, that it could index in the searching server(ES) in real
# time.
# until the instance related models get saved and start to
# serialize the data when its
By the way I am using django-admin and I am not defining the logics in
a view, adding the related model instance is handled by django admin
In this case, you can flip the order with which ModelAdmin calls save_model() and save_related() so from Model.save() you will be able to reach the updated values of the related fields, as stated in this post.
class Video(models.Model):
def save(self, *args, **kwargs):
if not self.id:
super().save(*args, **kwargs)
all_updated_keywards = self.keywards.all()
...
super().save(*args, **kwargs)
class VideoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not obj.pk:
super().save_model(request, obj, form, change)
else:
pass
def save_related(self, request, form, formsets, change):
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=change)
super().save_model(request, form.instance, form, change)
You can override model's save() method and save related models (objects) before saving instance.

django model save - override method not invoked during migrations

I have a save override method in my model class, which generates a new slug each time an object is saved.
def save(self, *args, **kwargs):
if self.column2:
self.slug = slugify(self.column1 + " " + self.column2)
else:
self.slug = slugify(self.column1)
print slug
super(MyModel, self).save(*args, **kwargs)
When I try to create a new object by logging into the python shell, I see the save method is being invoked.
python manage.py shell
>>> MyModel(column1="test",column2="2015").save()
slug is test-2015
However when I am running a migration, this save override method is not being called. Here's part of my migration script..
...
def add_myModel_details(apps, schema_editor):
x = apps.get_model("myapp","myModel")
MyModel(column1 = "test", column2="2015" ).save()
.....
The slug is empty, as the save override isn't being called.
Custom model methods are not available during migrations.
Instead, you can run put code in your RunPython function that modifies your model instances the way the custom save() would have.
References:
This answer
It happens because migrations don't call your save method.
I think save method is not the best place for generate slug. Will be better to use AutoSlugField or signals.
1. signals:
In your case you may use signal pre_save.
Example:
#receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
my_model = kwargs.get('instance')
if my_model.column2:
my_model.slug = slugify(my_model.column1 + " " + my_model.column2)
else:
my_model.slug = slugify(my_model.column1)
print my_model.slug
2. AutoSlugField:
It's not a standard field but a lot of libraries implement it. I use AutoSlugField from django-extensions. This field uses signals too.
Example:
slug = AutoSlugField(populate_from=("column1", "column2"))
3. save method and migrations
But if you still want to use a save method to generating slug I'd recommend you create data migration and add slugs manually.
Data Migrations django >= 1.7
Data Migrations south

how to modify related object's data whilie creating a new object in django

I am new to django and I'm trying to do something pretty simple.
my models.py is as below:
from django.db import models
class DiskDrive(models.Model):
deviceId = models.CharField(max_length=64, primary_key=True)
freeSpace = models.BigIntegerField()
def __unicode__(self):
return self.deviceId
class StoragePool(models.Model):
poolId = models.CharField(max_length=256, primary_key=True)
size = models.BigIntegerField()
drive = models.ForeignKey(DiskDrive, related_name='pools')
def __unicode__(self):
return self.poolId
I haven't added anything to views.py and urls.py yet.
I'm able to create objects of both the classes.
Whenever I create an object of StoragePool class, I want to reduce the value of 'freeSpace' attribute of the related DiskDrive object by the 'size' of 'StoragePool' object. How should I do this? Please help...
Sounds like the perfect job for a post_save signal:
#receiver(post_save, sender=StoragePool)
def update_drive_space(sender, instance, created, **kwargs):
if created:
instance.drive.freeSpace = F('freeSpace') - instance.size
instance.drive.save(update_fields=['freeSpace'])
See Django signals documentation for more info about how signals work. In a nutshell this method will be called each time you create or update a StoragePool object.
Few notes:
I am using F expression to reference current database size value instead of blindly saving whatever we have on Django side (this will ensure correct value when multiple clients will create new StoragePool objects)
It is likely that you want to adjust freeSpace attribute also when size is updated - not just on StoragePool creation. To make that happen just delete if created check and it will be run on every StoragePool.save()

Django admin error in many-to-many relationship

For example.
class One(models.Model):
text=models.CharField(max_length=100)
class Two(models.Model):
test = models.Integer()
many = models.ManyToManyField(One, blank=True)
When I try save my object in admin panel, I take error such as:
"'Two' instance needs to have a primary key value before a many-to-many relationship can be used."
I use django 1.3. I tried add AutoField to Two class, but it's not work too.
This is my code.
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.core.urlresolvers import reverse
from project.foo.forms import FooForm
from project.foo.models import Foo
from project.fooTwo.views import fooTwoView
def foo(request, template_name="foo_form.html"):
if request.method == 'POST':
form = FooForm(data=request.POST)
if form.is_valid():
foo = Foo()
foo.name = request.POST.get("name")
foo.count_people = request.POST.get("count_people")
foo.date_time = request.POST.get("date_time")
foo.save()
return fooTwoView(request)
else:
form = FooForm()
return render_to_response(template_name, RequestContext(request, {
"form": form,
}))
P.S. I find my fail. It is in model. I used many-to-many in save method. I add checking before using, but it's not help.
class Foo(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
count_people = models.PositiveSmallIntegerField()
menu = models.ManyToManyField(Product, blank=True, null=True)
count_people = models.Integer()
full_cost = models.IntegerField(blank=True)
def save(self, *args, **kwargs):
if(hasattr(self,'menu')):
self.full_cost = self.calculate_full_cost()
super(Foo, self).save(*args, **kwargs)
def calculate_full_cost(self):
cost_from_products = sum([product.price for product in self.menu.all()])
percent = cost_from_products * 0.1
return cost_from_products + percent
I try hack in save method such as
if(hasattr(self,Two)):
self.full_cost = self.calculate_full_cost()
This is help me, but i dont think that is the django way. What is interesting, that is without this checking admin panel show error, but create object. Now, if i select item from Two and save, my object does not have full_cost, but when i view my object, admin panel remember my choice and show me my Two item, what i select... I dont know why.
How do i save this?
There are quite a few problems with your code. The most obvious one are
1/ in your view, using a form for user inputs validation/sanitization/conversion then ignoring the santized/converted data and getting unsanitized inputs directly from the request. Use form.cleaned_data instead of request.POST to get your data, or even better use a ModelForm which will take care of creating a fully populated Foo instance for you.
2/ there's NO implicit "this" (or "self" or whatever) pointer in Python methods, you have to explicitely use "self" to get at the instance attributes. Here's what your model's "save" method really do:
def save(self, *args, **kwargs):
# test the truth value of the builtin "id" function
if(id):
# create a local variable "full_cost"
full_cost = self.calculate_full_cost()
# call on super with a wrong base class
super(Banquet, self).save(*args, **kwargs)
# and exit, discarding the value of "full_cost"
Now with regard to your question: Foo.save is obviously not the right place to compute someting based on m2m related objects. Either write a distinct method that run the computation AND update Foo AND save it and call it after the m2m are saved (hint : a ModelForm will take care of saveing the m2m related objects for you), or just use the m2m_changed signal.
This being said, I strongly suggest you spend a few hours learning Python and Django - it will save you a lot of time.
Why not use "OneToOneField" instead of Many-to-Many

Categories

Resources