Signals in Satchmo

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.

Signal Descriptions

External Signals Used in Satchmo

Satchmo depends on signals in satchmo_utils.signals and triggers them at various points of execution; below are some of them.

Sent by to ask all listeners to add search results.

Arguments sent with this signal:

The product.models.Product model (Note: not an instance of Product)
The HttpRequest object used in the search view
The category slug to limit a search to a specific category
A list of keywords search for

A dictionary of results to update with search results. The contents of the dictionary should contain the following information:

A QuerySet of product.models.Cateogry objects which matched the search criteria
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:

The module having url patterns added to it
The url patterns to be added. This is an instance of django.conf.urls.patterns
The name of the section adding the urls (Note: this argument is not always provided). For example ‘__init__’ or ‘product’


from 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:

The model of the form being initialized. The value of sender will be one of the models defined above.
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:


The form model of the form being set (Note: Not an instance). Possible values include:

  • payment.modules.purchaseorder.forms.PurchaseorderPayShipForm
  • payment.forms.CreditPayShipForm
  • payment.forms.SimplePayShipForm
  • payment.forms.PaymentContactInfoForm
  • The instance of the form defined by one of the above models that was saved.
  • A instance if the form being saved is an instance of otherwise this value does not exist.
  • The data associated with the form if the form being saved is an instance of otherwise this value does not exist.


Putting it All Together

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 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 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) = 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
        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 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
        return False

The final step is to make sure your new listener is hooked up. In your add the following code:

from listeners import veto_for_non_members
from 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.

Ensuring Acceptance of Terms during Checkout

The signal satchmo_utils.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/ file:

urlpatterns += patterns('',
url(r'^shop_terms/$', 'project-name.localsite.views.shop_terms',

Next, create the view in your /localsite/ 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',

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/

from payment.forms import SimplePayShipForm
from payment.listeners import form_terms_listener
from satchmo_utils.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.