Signals are a very powerful tool available in Django that allows you to decouple aspects of your application. The Django Signals Documentation, has this summary:
“In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place.”
In addition to all of the built in Django signals, Satchmo includes a number of store related signals. By using these signals, you can add very unique customizations to your store without needing to modify the Satchmo code.
Base class for all signals
Internal attributes:
- receivers
- { receriverkey (id) : weakref(receiver) }
Sent when an order is complete and the balance goes to zero during a save.
Parameters: |
|
---|
Note
order argument is the same as sender.
Sent when an order is about to be cancelled and asks listeners if they allow to do so.
By default, orders in states ‘Shipped’, ‘Completed’ and ‘Cancelled’ are not allowed to be cancelled. The default verdict is stored in order.is_cancellable flag. Listeners can modify this flag, according to their needs.
Parameters: |
|
---|
Note
order argument is the same as sender.
Sent when an order has been cancelled; it’s status already reflects it and has been saved to the database (e.g. payment gateway cancels payment).
Parameters: |
|
---|
Note
order argument is the same as sender.
Sent after an item has been successfully added to the cart.
Parameters: |
|
---|
Note
cart is the same as sender.
Sent before an item is added to the cart.
Parameters: |
|
---|
Note
cart is the same as sender.
Sent whenever the status of the cart has changed. For example, when an item is added, removed, or had it’s quantity updated.
Parameters: |
|
---|
Note
cart is the same as sender.
Sent by the pricing system to allow price overrides when displaying line item prices.
Parameters: |
|
---|
Note
cartitem is the same as sender.
Sent before an item is added to the cart so that listeners can update product details.
Parameters: |
|
---|
Note
cart is the same as sender.
Sent after each item from the cart is copied into an order.
Parameters: |
|
---|
Sent when satchmo_store.shop.context_processors.settings() is invoked, before the context is returned. This signal can be used to modify the context returned by the context processor.
Parameters: |
|
---|
Sent by ‘views.smart_add` to allow listeners to optionally change the responding function.
Parameters: |
|
---|
Note
sender is not a class instance.
Sent to determine where to redirect a user for a DownloadableProduct.
Parameters: |
|
---|
Warning
For a sane filename parameter in the Content-Disposition header, users are cautioned against appending a trailing slash('/') to the URL.
Sent by satchmo_store.mail.send_store_mail() before the message body is rendered.
Takes the same arguments as sending_store_mail.
Note
send_mail_args does not contain the 'subject' entry.
Note
If the 'message' entry is set in send_mail_args by a listener, it will be used instead of the rendered result in send_store_mail().
Sent by satchmo_store.mail.send_store_mail() just before send_mail() is invoked.
Listeners may raise satchmo_store.mail.ShouldNotSendMail.
If they choose to invoke django.mail.EmailMessage.send(), any errors raised will be handled by send_store_mail(); they should consequently raise ShouldNotSendMail to avoid re-sending the email.
Parameters: |
|
---|
Parameters: |
|
---|
Note
If the context argument to send_store_mail() contains the entry send_mail_args, it will not be available in the listener’s context dictionary.
Example:
from satchmo_store.shop.signals import order_notice_sender
def modify_subject(sender, send_mail_args={}, context={}, **kwargs):
if not ('shop_name' in context and 'order' in context):
return
send_mail_args['subject'] = '[%s] Woohoo! You got a *new* order! (ID: #%d)' % \
(context['shop_name'], context['order'].id)
sending_store_mail.connect(modify_subject, sender=order_notice_sender)
Sent when contact information is viewed or updated before a template is rendered. Allows you to override the contact information and context passed to the templates used.
Parameters: |
|
---|
Note
contact is the same as sender.
Sent after a user changes their location in their profile.
Parameters: |
|
---|
Sent when a form that contains postal codes (shipping and billing forms) needs to validate. This signal can be used to custom validate postal postal codes. Any listener should return the validated postal code or raise an exception for an invalid postal code.
Parameters: |
|
---|
Sent after a user has registered an account with the store.
Parameters: |
|
---|
Sent after a user account has been verified. This signal is also sent right after an account is created if account verification is disabled.
Parameters: |
|
---|
Sent after a newsletter subscription has been updated.
Parameters: |
|
---|
Sent after ensuring that the cart and order are valid.
Parameters: |
|
---|
Note
sender is the same as controller.
Sent when a payment.forms.PaymentMethodForm is initialized. Receivers have cart/order passed in variables to check the contents and modify methods list if neccessary.
Parameters: |
|
---|
The following example shows how to conditionally modify the payment choices presented to a customer:
def adjust_payment_choices(sender, contact, methods, **kwargs):
if should_reduce: # whatever your condition is
for method in methods:
if method[0] == 'PAYMENT_PMTKEY':
methods.remove(method)
Sent after a list of payment choices is compiled, allows the editing of payment choices.
Parameters: |
|
---|
Sent before an index is rendered for categories or brands.
Parameters: |
|
---|
Note
category and brand will not be passed for category listings.
Sent before returning the price of a product.
Parameters: |
|
---|
Note
slug and discountable are only sent by product.models.ProductPriceLookup.
Note
price is the same as sender
Sent when a downloadable product is successful.
Parameters: |
|
---|
Sent to verify the set of order items that are subject to discount.
Listeners should modify the “discounted” dictionary to change the set of discounted cart items.
Parameters: |
|
---|
Satchmo depends on signals in signals_ahoy.signals and triggers them at various points of execution; below are some of them.
Sent by satchmo_store.shop.views.search.search_view() to ask all listeners to add search results.
Arguments sent with this signal:
- sender
- The product.models.Product model (Note: not an instance of Product)
- request
- The HttpRequest object used in the search view
- category
- The category slug to limit a search to a specific category
- keywords
- A list of keywords search for
- results
A dictionary of results to update with search results. The contents of the dictionary should contain the following information:
- categories
- A QuerySet of product.models.Cateogry objects which matched the search criteria
- products
- A Queryset of product.models.Product objects which matched the search critera
Sent by urls modules to allow listeners to add or replace urls to that module
Arguments sent with this signal:
- sender
- The module having url patterns added to it
- patterns
- The url patterns to be added. This is an instance of django.conf.urls.defaults.patterns
- section
- The name of the section adding the urls (Note: this argument is not always provided). For example ‘__init__’ or ‘product’
Example:
from satchmo_store.shop.signals import satchmo_cart_add_complete import myviews satchmo_cart_add_complete.connect(myviews.cart_add_listener, sender=None)
Sent when a contact info form is initialized. Contact info forms include:
- contact.forms.ContactInfoForm
- contact.forms.ExtendedContactInfoForm
- payment.forms.PaymentContactInfoForm
Arguments sent with this signal:
- sender
- The model of the form being initialized. The value of sender will be one of the models defined above.
- form
- An instance of the form (whose type is defined by sender) being intitialized.
See Ensuring Acceptance of Terms during Checkout for an example of how this signal can be used.
Sent after a form has been saved to the database
Arguments sent with this signal:
- sender
The form model of the form being set (Note: Not an instance). Possible values include:
- satchmo_store.contact.forms.ContactInfoForm
- payment.modules.purchaseorder.forms.PurchaseorderPayShipForm
- payment.forms.CreditPayShipForm
- payment.forms.SimplePayShipForm
- payment.forms.PaymentContactInfoForm
- form
- The instance of the form defined by one of the above models that was saved.
- object
- A satchmo_store.contact.models.Contact instance if the form being saved is an instance of satchmo_store.contact.forms.ContactInfoForm otherwise this value does not exist.
- formdata
- The data associated with the form if the form being saved is an instance of satchmo_store.contact.forms.ContactInfoForm otherwise this value does not exist.
This section contains a brief example of how to use signals in your application. For this example, we want to have certain products that are only available to members. Everyone can see the products, but only members can add to the cart. If a non-member tries to purchase a product, they will get a clear error message letting them know they need to be a member.
The first thing to do is create a listeners.py file in your app. In this case, the file would look something like this:
"""
A custom listener that will evaluate whether or not the product being added
to the cart is available to the current user based on their membership.
"""
from satchmo_store.shop.exceptions import CartAddProhibited
from django.utils.translation import gettext_lazy as _
class ContactCannotOrder(CartAddProhibited):
def __init__(self, contact, product, msg):
super(ContactCannotOrder, self).__init__(product, msg)
self.contact = contact
def veto_for_non_members(sender, cartitem=None, added_quantity=0, **kwargs):
from utils import can_user_buy
customer = kwargs['cart'].customer
if can_user_buy(cartitem.product, customer):
return True
else:
msg = _("Only members are allowed to purchase this product.")
raise ContactCannotOrder(customer, cartitem.product, msg)
Next, you need to create the can_user_buy function. Your utils.py file could look something like this (details left up to the reader):
def can_user_buy(product, contact=None):
"""
Given a product and a user, return True if that person can buy it and
False if they can not.
This doesn't work as it stands now. You'll need to customize the
is_member function
"""
if is_member(contact):
return True
else:
return False
The final step is to make sure your new listener is hooked up. In your models.py add the following code:
from listeners import veto_for_non_members
from satchmo_store.shop import signals
signals.satchmo_cart_add_verify.connect(veto_for_non_members, sender=None)
Now, you should be able to restrict certain products to only your members. The nice thing is that you’ve done this without modifying your satchmo base code.
The signal signals_ahoy.signals.form_init() can be combined with the payment.listeners.form_terms_listener to add a custom terms and conditions acceptance box into your checkout flow.
First, add the terms view in your /localsite/urls.py file:
urlpatterns += patterns('',
url(r'^shop_terms/$', 'project-name.localsite.views.shop_terms',
name="shop_terms"),
)
Next, create the view in your /localsite/views.py to display the terms:
from django.shortcuts import render_to_response
from django.template import RequestContext
def shop_terms(request):
ctx = RequestContext(request, {})
return render_to_response('localsite/shop-terms.html',
context_instance=ctx)
Now, you will need modify the checkout html to display the new form. Copy /satchmo/apps/payment/templates/shop/checkout/pay_ship.html to /project-name/templates/shop/checkout/pay_ship.html.
Add the following code to the copied pay_ship.html to display the form:
{{ form.terms }} {{ form.terms.label|safe }}
{% if form.terms.errors %}<br/>**{{ form.terms.errors|join:", " }}{% endif %}
Make sure you register the forms_terms_listener by adding the following code to your /localsite/models.py:
from payment.forms import SimplePayShipForm
from payment.listeners import form_terms_listener
from signals_ahoy.signals import form_init
form_init.connect(form_terms_listener, sender=SimplePayShipForm)
The final step is to create your actual store-name/templates/localsite/shop-terms.html, like this:
{% extends "base.html" %}
{% block content %}
<p>Put all of your sample terms here.</p>
{% endblock %}
Now, when users checkout, they must agree to your store’s terms.