I need some help with sending email when an order is placed. To illustrate the problem, following is the abstract code:
class Order(models.Model):
user = models.ForeignKey(User)
class OrderItem(modes.Model):
order = models.ForeignKey(Order, related_name='items')
item = models.CharField(max_length=255)
unit_price = models.DecimalField()
qty = models.IntegerField()
item_amount = models.DecimalField()
def email_order_on_save(sender, instance, **kwargs):
# Need order.items.all() here
pass
post_save.connect(email_order_on_save, sender=Order)
Most of the problems on SO and google seem to deal with one child object at a time; such as this.
Listening to OrderItem would release 5 signals if 5 orders items saved from admin inlines. I can't seem to get my head around this problem. One way, I think (not sure if possible), could be listening to last of all(5) OrderItem's post_save signals.
Any help appreciated.
I'm guessing you're trying to solve this in the wrong place. Sending an email when the order is completed and saving the Order model are at different levels of abstraction.
I think sending the email should be triggered by some condition in the view that has more information about whether the order is completely saved or not. Think for example of what will happen if an order needs updating (say it's status changes)? Should the email be sent then too?
Create your own custom signal and send it at the point when you have the data you need saved. Pass in as parameters whatever data structures you need.
Listen for your custom signal in your callback function email_order_on_save and make appropriate decisions based on the parameters about sending or not the e-mail.
You could create your model as follows
ORDER_STATE = (
(1, 'Completed'),
(2, 'Processing'),
)
class Order(models.Model):
user = models.ForeignKey(User)
state = models.IntegerField(choices = ORDER_STATE)
You could have many states for the order. The state "Completed" could represent that the order processing is complete. You could change the state of your order in your views.
In the signal handler, you could check for the state of the order and then send mail, if the order is in completed state.
I think you can have a problem with signals, OrderItem with inlines will not send save signal, read this
Related
I'm new to django, but working on an app for a volunteer sailing organization in my local area. Not sure how to ask this question since it's fairly general but I want the following to happen based on two models;
Yacht class (boat name, skipper, color, etc.)
Race_Event class (event date, time results for each boat)
Step 1: The user will need to create a Race_Event each week. I want the boats from the Yacht model to be loaded into the Race_Event.
Step 2: The user will enter race times for each boat.
Is there a way to pre-load objects from one model into another? With a ForeignKey the user has to add the boats each time. Any direction for me to research would be helpful.
Here is the simplified code so far;
class Yacht (models.Model):
yacht_classes = [('A', 'A'),('A1', 'A1'),]
yacht_type = [('J-29','J-29'),('J-24','J-24'),]
yacht_name = models.CharField(max_length=75)
yacht_type = models.CharField(max_length=25, choices=yacht_type,
default='J-29')
yacht_class = models.CharField(max_length=25, choices=yacht_classes)
skipper = models.ForeignKey(Skipper, on_delete=models.CASCADE)
def __str__(self):
return self.yacht_name
class Event (models.Model):
race_date = models.DateTimeField(default=timezone.now)
#yachts = #how to Include Yacht.objects.all() to the field?
class Results (models.Model):
pass
Thanks
Yes, u can use signals...
after objects is saved u can call post_save and add all yachts to race
more => https://docs.djangoproject.com/en/3.1/ref/signals/#post-save
but i dont think this is good way...
(not every time all the data must be present or must be saved => this save rows in database)
i recomment you to use m2M between race and ship with throught table where time is saved in table between.
then its on you how you present this problem to end-user.
with this solution you save only data which are needed.
this can be done with
https://docs.djangoproject.com/en/3.1/topics/db/models/#extra-fields-on-many-to-many-relationships
Assuming the following models schema,
Parent model:
class Batch(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
One of many child models:
class Data(models.Model):
batch = models.ForeignKey(Batch, on_delete=models.ON_CASCADE)
timestamp = models.DateTimeField()
My goals is the following: to have a start field of parent model that is always updated when any child model is modified.
Basically, if the timestamp of a newly data instance is older than the start field I want the start field to be updated to that instance timestamp value. In the case of deletion of the data instance which is the oldest time reference point I want batch start field to be updated to the second oldest. Vice-versa for the end field.
One of the possible way to do this is to add post or pre-save signal of relative models and Update your necessary fields according to this. Django official documentation for signal, link. I want to add another link, one of the best blog post i have seen regarding django signal.
Edit for André Guerra response
One of easiest way to do a get call and bring Batch instance. What i want to say
#receiver(post_save,sender=Data)
def on_batch_child_saving(sender,instance,**kwargs):
batch_instance = Batch.objects.get(pk=instance.batch)
if (instance.timestamp < batch_instance.start):
batch_instance.start = instance.timestamp
batch_instance.save()
elif (instance.timestamp > batch_instance.end):
batch_instance.end = instance.timestamp
batch_instance.save()
Based on Shakil suggestion, I come up with this: (my doubt here was on how to save the parent model)
#receiver(post_save,sender=Data)
def on_batch_child_saving(sender,instance,**kwargs):
if (instance.timestamp < instance.batch.start):
instance.batch.start = instance.timestamp
instance.batch.save()
elif (instance.timestamp > instance.batch.end):
instance.batch.end = instance.timestamp
instance.batch.save()
I thought about my problem for days and i need a fresh view on this.
I am building a small application for a client for his deliveries.
# models.py - Clients app
class ClientPR(models.Model):
title = models.CharField(max_length=5,
choices=TITLE_LIST,
default='mr')
last_name = models.CharField(max_length=65)
first_name = models.CharField(max_length=65, verbose_name='Prénom')
frequency = WeekdayField(default=[]) # Return a CommaSeparatedIntegerField from 0 for Monday to 6 for Sunday...
[...]
# models.py - Delivery app
class Truck(models.Model):
name = models.CharField(max_length=40, verbose_name='Nom')
description = models.CharField(max_length=250, blank=True)
color = models.CharField(max_length=10,
choices=COLORS,
default='green',
unique=True,
verbose_name='Couleur Associée')
class Order(models.Model):
delivery = models.ForeignKey(OrderDelivery, verbose_name='Delivery')
client = models.ForeignKey(ClientPR)
order = models.PositiveSmallIntegerField()
class OrderDelivery(models.Model):
date = models.DateField(default=d.today())
truck = models.ForeignKey(Truck, verbose_name='Camion', unique_for_date="date")
So i was trying to get a query and i got this one :
ClientPR.objects.today().filter(order__delivery__date=date.today())
.order_by('order__delivery__truck', 'order__order')
But, i does not do what i really want.
I want to have a list of Client obj (query sets) group by truck and order by today's delivery order !
The thing is, i want to have EVERY clients for the day even if they are not in the delivery list and with filter, that cannot be it.
I can make a query with OrderDelivery model but i will only get the clients for the delivery, not all of them for the day...
Maybe i will need to do it with a Q object ? or even raw SQL ?
Maybe i have built my models relationships the wrong way ? Or i need to lower what i want to do... Well, for now, i need your help to see the problem with new eyes !
Thanks for those who will take some time to help me.
After some tests, i decided to go with 2 querys for one table.
One from OrderDelivery Queryset for getting a list of clients regroup by Trucks and another one from ClientPR Queryset for all the clients without a delivery set for them.
I that way, no problem !
Consider the following:
class OrderForm(models.Model):
title = models.CharField(max_length=100)
desc = models.TextField()
class OrderFormLine(models.Model):
order = models.ForeignKey(OrderForm)
lagel = models.CharField(max_length=100)
qty = models.IntegerField(...)
price = models.FloatField(...)
Now I want to send an email with the orderform details whenever someone creates one or modify one.
No rocket science so far .. let's just use a post_save signal;
post_save.connect(email_notify, sender=OrderForm)
But there's one tiny problem, the OrderForm object passed to email_notify is updated with the new data as expected, but not the related OrderFormLine items.
I've tried to override the save methods in the admin AND in the model, I've tried to save the object, the form and its relation before passing it to my notification handler, nothing works.
I'm aware that I could attach the post_save signal to the OrderItem model, but then the email would be sent for each items.
Help I'm on the brink of madness.
UPDATE:
Found a simple and reliable solution
Short story:
def email_notify_orderform(sender, **kwargs):
instance = kwargs['instance']
ct = ContentType.objects.get_for_model(OrderForm)
if ct.id == instance.content_type.id:
print instance.is_addition()
print instance.is_change()
print instance.is_deletion()
print instance.change_message
print instance.action_time
print instance.get_edited_object().total() # BINGO !
post_save.connect(email_notify_orderform, sender=LogEntry)
The basic problem is that when the main objects post_save signal is sent, the inlines have not been saved yet: the parent model always gets saved first. So, it's not that it's sending old data; in fact it's the current state of the data.
The simplest solution is to create a custom signal and have that signal sent at a place where the inlines have been saved. The save_formset method on ModelAdmin is your hook.
I am working with django and having a hard time grasping how to do complex queries
Here is my model
class TankJournal(models.Model):
user = models.ForeignKey(User)
tank = models.ForeignKey(TankProfile)
ts = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=50)
body = models.TextField()
class Meta:
ordering = ('-ts',)
get_latest_by = 'ts'
I need to pull the username given the tank object.
The user object is the one built into django.. thanks!
EDIT:
I have tried this
print User.objects.filter(tankjournal__tank__exact=id)
It seems to not pull out just the id.. and pull out everything in tankjournal and match it to the tank object
If you already have your tank object you should be able to do:
tank.user.username
To reduce the database queries you might want to consider the use of select_related(), e.g.
tanks = TankJournal.objects.all().select_related()
for tank in tanks:
username = tank.user.username
if you have a specific tank id then:
tank = TankJournal.objects.select_related().get(id=123456)
username = tank.user.username
I may be misunderstanding your question, but a request on User.objects.filter() will return a list of User objects, not User ids. What you've written looks technically correct.
Remember, though, that the model you have sets up a one-to-many between the TankProfile object and the TankJournal. In other words, a single TankProfile can be associated with more than one TankJournal, and therefore to more than one user. Given this, you're query is doing the right thing, returning more than one User.