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.
-
satchmo_utils.signals.
application_search
()¶
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 viewcategory
- 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
ofproduct.models.Cateogry
objects which matched the search criteriaproducts
- A
Queryset
ofproduct.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 ofsatchmo_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.