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

satchmo_store.shop.signals.order_success(sender, order=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.order_cancel_query(sender, order=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.order_cancelled(sender, order=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.satchmo_cart_add_complete(sender, cart=None, cartitem=None, product=None, request=None, form=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.satchmo_cart_add_verify(sender, cart=None, cartitem=None, added_quantity=None, details=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.satchmo_cart_changed(sender, cart=None, request=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.satchmo_cartitem_price_query(sender, cartitem=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.satchmo_cart_details_query(sender, product=None, quantity=None, details=None, request=None, form=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.satchmo_post_copy_item_to_order(sender, cartitem=None, order=None, orderitem=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.satchmo_context(sender, context=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.cart_add_view(sender, request=None, method=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.sendfile_url_for_file(sender, file=None, product=None, url_dict={}, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.rendering_store_mail(sender, send_mail_args={}, context={}, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.shop.signals.sending_store_mail(sender, send_mail_args={}, context={}, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.contact.signals.satchmo_contact_view(sender, contact=None, contact_dict=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.contact.signals.satchmo_contact_location_changed(sender, contact=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.contact.signals.validate_postcode(sender, postcode=None, country=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.accounts.signals.satchmo_registration(sender, contact=None, subscribed=None, data=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
satchmo_store.accounts.signals.satchmo_registration_verified(sender, contact=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
payment.signals.confirm_sanity_check(sender, controller=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
payment.signals.payment_methods_query(sender, methods=None, cart=None, order=None, contact=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
payment.signals.payment_choices(sender, choices=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
product.signals.index_prerender(sender, request=None, context=None, category=None, brand=None, object_list=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
product.signals.satchmo_price_query(sender, price=None, slug=None, discountable=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
product.signals.subtype_order_success(sender, product=None, order=None, subtype=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }
product.signals.discount_filter_items(sender, discounted=None, order=None, **kwargs)

Base class for all signals

Internal attributes:

receivers
{ receriverkey (id) : weakref(receiver) }

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 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
satchmo_utils.signals.collect_urls()

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.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)
satchmo_utils.signals.form_init()

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.

satchmo_utils.signals.form_postsave()

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.

Examples

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 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.

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/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 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.