- About Pinax
- Important Links
- Overview
- Documentation
- Change Log
- History
- Contribute
- Code of Conduct
- Connect with Pinax
- License
Pinax is an open-source platform built on the Django Web Framework. It is an ecosystem of reusable Django apps, themes, and starter project templates. This collection can be found at http://pinaxproject.com.
Where you can find what you need:
- Releases: published to PyPI or tagged in app repos in the Pinax GitHub organization
- Global documentation: Pinax documentation website
- App specific documentation: app repos in the Pinax GitHub organization
- Support information: SUPPORT.md file in the Pinax default community health file repo
- Contributing information: CONTRIBUTING.md file in the Pinax default community health file repo
- Current and historical release docs: Pinax Wiki
pinax-referrals provides a site with the ability for users to
publish referral links to specific pages or objects and then record
any responses to those links for subsequent use by the site.
For example, on an object detail page, the site builder would use a
template tag from pinax-referrals to display a referral link that the user of the
site can send out in a Tweet. Upon clicking that link, a response to that
referral code will be recorded as well as any other activity that the site
builder wants to track for that session.
It is also possible for anonymous referral links/codes to be generated which is useful in marketing promotions and the like.
| Django / Python | 3.10 | 3.11 | 3.12 | 3.13 | 3.14 |
|---|---|---|---|---|---|
| 3.2 | * | ||||
| 4.2 | * | * | * | ||
| 5.2 | * | * | * | * | * |
| 6.0 | * | * | * | ||
| 6.1 | * | * | * |
To install pinax-referrals:
$ pip install pinax-referralsAdd pinax.referrals to your INSTALLED_APPS setting:
INSTALLED_APPS = [
# other apps
"pinax.referrals",
]See the list of settings to modify pinax-referrals's default
behavior and make adjustments for your website.
Add pinax.referrals.middleware.SessionJumpingMiddleware in order to link up a
user who registers and authenticate after hitting the initial referral link.
Make sure that it comes after the django.contrib.auth.middleware.AuthenticationMiddleware:
MIDDLEWARE = [
# other middleware
"django.contrib.auth.middleware.AuthenticationMiddleware",
"pinax.referrals.middleware.SessionJumpingMiddleware",
]Lastly add pinax.referrals.urls to your urls definition:
urlpatterns = [
# other urls
url(r"^referrals/", include("pinax.referrals.urls", namespace="pinax_referrals")),
]This is the factory method that the create_referral view calls but it can
be called directly in case you needed to integrate with pinax-referrals in
a different way.
For example, you might want to automatically give every user a referral code
that is emailed to them upon signup. In this case, you could created a one-to-one
relationship between their Profile and pinax-referrals' Referral and
create a signal receiver for when the Profile is created that calls:
referral = Referral.create(
user=profile.user,
redirect_to=reverse("home")
)
profile.referral = referral
profile.save()Then you could, in the welcome email that you send them upon signup, render their referral url that they could forward on to other users:
{{ user.get_profile.referral.url }}The only required parameter for Referral.create is redirect_to. It must
be a URL on your own site (a path such as /signup/, or an absolute URL on
the host serving the referral link or on a host listed in
PINAX_REFERRALS_ALLOWED_REDIRECT_HOSTS):
the create_referral view rejects any other value with a 400 response, and
process_referral redirects to / instead of following a stored
redirect_to that points elsewhere. If
you don't specify a user it will be recorded as None. This can be useful
if you want to attach a relationship between some object in your system
to a referral that might not have a user associated with it.
You can also pass in an optional label kwarg to Referral.create if
you want to allow your users to create and manage multiple referrals as
labeling them becomes important in order to keep track of them.
At runtime, you can append the redirect_to URL parameter to your referral
URL to dynamically redirect to an alternate destination.
{{ user.get_profile.referral.url }}?redirect_to=/special/The URL parameter used can be altered in your settings file.
A referral stops counting once its expired_at time has passed. Following
the link still redirects to its redirect_to, so links already shared do
not break, but no response is recorded and no referral cookie is set.
Referral.record_response records nothing more for it, and
SessionJumpingMiddleware no longer links its earlier responses to a user
who logs in. A referral whose expired_at is None (the default) never
expires. Referral.is_active() tells you whether a referral still counts.
referral.expired_at = timezone.now() + timedelta(days=30)
referral.save()The classmethod record_response will attempt to see if the current user or
session has any previous responses to an initial referral and, if so, will then
proceed to record the response action.
For example, say you want to record the fact that the user did some site activity after clicking on the referral link you tweeted and subsequently decided to register and login to the site:
from pinax.referrals.models import Referral
def my_view(request, **kwargs):
# other code
referral_response = Referral.record_response(request, "SOME_ACTION")In this case the referral_response will be None if the user on the request
doesn't have any previously recorded referral responses. In addition, if the user
has responded to more than one Referral code, then this will associate the
activity with the most recent response.
In addition, this supports passing an options target keyward argument, if
you wanted to record associations with specific objects. For example:
Referral.record_response(request, "SOME_ACTION", target=some_object)This will record a generic foreign key to some_object that you can use elsewhere
to identify activity from your referral at a deeper level than just based on
the action label.
This class method will give you a referral object for the given request in case you need to apply any business logic in your project. For example, to do any comparisons on the referral.target with another object you have in context for segmenting permissions or authorizations to make your referral system more fine grained.
Defaults to None
The total amount of time (in seconds) the cookie lasts in the client's browser.
The pinax-referral cookie holds the visitor's session key. It is set
HttpOnly and SameSite=Lax, and Secure when the referral link was
followed over HTTPS.
Defaults to "HTTP_X_FORWARDED_FOR"
This is the header value that is retrieved from request.META to record
the ip address of the the respondent.
Defaults to "pinax.referrals.callbacks.get_client_ip"
If the default IP determining function doesn't suit you, supply a path with your own function (takes a request as a parameter).
Defaults to False
Setting this to True will produce urls with https instead of http.
Defaults to "pinax.referrals.callbacks.generate_code"
Externalizes the logic that generates the referral code. pinax-referrals ships
with a default that will generate a random 40-character alpha-numeric
string that can also be used as a reference implementation. The callable
defined by the fully qualified path is passed the class of the referral model (or Referral)
and the actual referral model instance. This means that you can easily customize the code for each user.
For example:
def generate_code(referral_class, referral):
return referral.user.usernameSince only one Referral with the same code can exist at the same time the value
returned by generate_code needs to be unique.
Defaults to {"RESPONDED": "Clicked on referral link"}
Defines a dictionary mapping action codes for responses to user-friendly
display text. Used by the action_display template filter.
Defaults to redirect_to
Defines the URL attribute to retrieve dynamic referral redirection URLs from.
The URL is checked by django.utils.http.url_has_allowed_host_and_scheme,
so if it points to a host other than the one serving the request (or one
listed in PINAX_REFERRALS_ALLOWED_REDIRECT_HOSTS), the view will redirect
to Referral.redirect_to instead.
Defaults to []
Hosts that referral links may redirect to in addition to the host serving
the request, for sites that serve referral links on one domain and land
visitors on another (for example links on www.example.com that open
app.example.com). Entries are matched like Django's ALLOWED_HOSTS:
"www.example.com" matches that host only, and ".example.com" matches
example.com and all of its subdomains. A port in the target URL is ignored.
Unlike ALLOWED_HOSTS, "*" matches nothing, because allowing every host
would turn referral links back into an open redirect.
PINAX_REFERRALS_ALLOWED_REDIRECT_HOSTS = ["app.example.com", ".example.org"]The list applies to redirect_to when create_referral stores it, to the
stored redirect_to when a link is followed, and to the redirect_to URL
parameter. On a request served over HTTPS, an absolute target must use
https.
user_linked_to_response is a signal that provides the single argument of a
response object that has just been linked to a user. You can use this to
provide further automatic processing within your site, such as adding
permissions, etc. to users that signup as a result of a referral.
For example:
@receiver(user_linked_to_response)
def handle_user_linked_to_response(sender, response, **kwargs):
if response.action == "SOME_SPECIAL_ACTION":
pass # do something with response.user and response.referral.target (object that referral was linked to)pinax-referrals comes with a single template fragment for rendering a simple
form that is used in creating a referral link.
This is a snippet that renders the form that upon submission will create the
referral link. By default it is rendered with the class referral with the
following context variables:
{
"url": url,
"obj": obj,
"obj_ct": ContentType.objects.get_for_model(obj)
}or if no object was passed into the create_referral template tag then
the context would simply blank for obj and obj_ct.
In order to use pinax-referrals in your project you will use the
create_referral template tag wherever you'd like a user to be able to get a
referral link to a page or a particular object:
{% load pinax_referrals_tags %}
{% create_referral object.get_absolute_url object %}The use of object in this case is optional if you just want to record
referrals to a particular url. In that case you can just do:
{% load pinax_referrals_tags %}
{% url my_named_url as myurl %}
{% create_referral myurl %}This will render a form that is defined in pinax/referrals/_create_referral_form.html
which will POST to a view and return JSON. The intent of this form is that it
be used with an AJAX submission and handler.
The recommended way is to use jquery.form and to do the following:
$(function () {
$('form.referral').each(function(i, e) {
var form = $(e);
options = {
dataType: "json",
success: function(data) {
form.html('<input type="text" value="' + data.url + '" />');
form.find("input[type=text]").select();
}
}
form.ajaxForm(options);
});
});This template tag is an assignment tag that, given a user, sets a context variable with a queryset of all responses for all referrals owned by the user, in order of when they were created.
The use case for this is displaying all the activities associated with the user's different labeled referrals.
Example:
{% load pinax_referrals_tags %}
{% referral_responses user as responses %}
{% for response in responses %}
{# response is a ReferralResponse object #}
{% endfor %}This filter converts a response code into a user friendly display of what that
code means. The definitions exist in the setting PINAX_REFERRALS_ACTION_DISPLAY.
{% load pinax_referrals_tags %}
<p>
{{ response.action|action_display }}
</p>If you need to make migrations for pinax-referrals, run:
$ python manage.py makemigrations referralsYou may need to do this if you use a custom user model and upgrade Django.
- Security: fixed an open redirect. Any logged-in user could POST an external
redirect_toto thecreate_referralview and get a referral link on your domain that bounced visitors to that external site.create_referralnow rejects aredirect_tothat is not on the requesting host (400), andprocess_referralno longer follows a stored off-siteredirect_to; it redirects to/instead. Behaviour change: existing referrals whoseredirect_topoints to another host, including ones created directly withReferral.create, now land on/instead of that host. - Added the
PINAX_REFERRALS_ALLOWED_REDIRECT_HOSTSsetting. Sites whose referral links redirect to another of their own hosts or subdomains (for example fromwwwtoapp) list those hosts there; without it, the fix above sends those visitors to/. Referral.expired_atis now enforced (#7). An expired referral link still redirects, but records no response and sets no referral cookie;Referral.record_responserecords nothing for it; andSessionJumpingMiddlewareno longer links its responses to a user who logs in. AddedReferral.is_active(), andexpired_atto the admin list and filters. Behaviour change: referrals that already have anexpired_atin the past stop counting.- Dropped support for Python 3.8 and 3.9, which are past end of life;
python_requiresis now>=3.10. Django 3.2 is now tested on Python 3.10 only, and Django 4.2 on Python 3.10-3.12. - Now tested against Django 3.2, 4.2, 5.2, 6.0, 6.1 and main, on Python 3.10-3.14
- The
pinax-referralcookie, which holds the visitor's session key, is nowHttpOnlyandSameSite=Lax, andSecurewhen set over HTTPS. Behaviour change: JavaScript can no longer read it. - Fixed a server error (500) in
process_referralwhen theredirect_toURL parameter or a storedredirect_tois a bare word that is not a URL name (e.g.?redirect_to=foo). It now falls back to the next target, as for an off-site URL. - Fixed a 500 on every request of a logged-in user whose
pinax-referralcookie has no:, inSessionJumpingMiddleware(and inReferral.for_request). The cookie is dropped instead. create_referralreturns 400 instead of a 500 whenobj_ct_pkandobj_pkdo not identify an existing object.Referral.referral_for_request, and soReferral.record_response, now fetches only the visitor's latest response instead of all of them.
- Dropped support for Django < 3.2 and Python < 3.8
- Now tested against Django 3.2, 4.2, 5.2, 6.0 and main, on Python 3.8-3.13
- Added
python_requiresso pip no longer installs the package on unsupported Python versions - Removed compatibility shims that the new baseline makes dead code
(
default_app_config, theMiddlewareMixinfallback, theis_safe_urlfallback, and thepkg_resourcesfallback) - Removed the
distutilsdependency from the test runner - Updated the linting toolchain (flake8 7, isort 5)
- added tolerance to the ending slash in URL
- ReferralResponse now stores http_referrer
- Updated testing. Tests now passes in Django 2.2-5.1 and Python 3.7-3.12
- Added referral model instance parameter to
generate_codecallback (PINAX_REFERRALS_CODE_GENERATOR_CALLBACKvariable)
- Update to support Django 2.2-4.1, Python 3.7-3.10
- Fixed admin views performance
- Fixed AttributeError when
referral_responses_for_request()was called with WSGIRequest - Added customizable callback for
get_client_ipthroughPINAX_REFERRALS_GET_CLIENT_IP_CALLBACKvariable - Fixed potential phishing attack risk in
redirect_toparameter. Now the parameter is checked withdjango.utils.http.url_has_allowed_host_and_schemeto prevent redirection to unsafe domain.
- Added setting to set the cookie's max age in client's browser
- Changes to admin.py and models.py to increase overridability
- Drop Django 1.11, 2.0, and 2.1, and Python 2,7, 3.4, and 3.5 support
- Add Django 2.2 and 3.0, and Python 3.6, 3.7, and 3.8 support
- Update packaging configs
- Direct users to community resources
- Simple fix for #46. Increase the length of the ip_address, so IPv6 address array from HTTP_X_FORWARDED_FOR can be stored.
- Fixing search in referral admin against "user"
- Changes to setup.py
- Use Django 2.0
reverseimport
- Change
is_authenticatedto property in models.py
- Add Django 2.0 compatibility testing
- Drop Django 1.9 and Python 3.3 support
- Convert CI and coverage to CircleCi and CodeCov
- Add PyPi-compatible long description
- Add URL namespacing
- Standardize documentation layout
- Drop Django v1.8, v1.10 support
- Update installation requirements for django>=1.11
- Remove unused doc build support
- Renamed from
anaferotopinax-referrals - Brought up to date with latest Pinax app standards
- Fix deprecation warning in urls
- ENHANCEMENT: made GFK fields on
Referralblankable
- ENHANCEMENT: added a signal to provide notifications of when a referred user authenticates
- BUG: Fix issue where target response is None
- FEATURE: added ability to record a specific object in reference to each response
ALTER TABLE "anafero_referralresponse"
ADD COLUMN "target_content_type_id" integer REFERENCES "django_content_type" ("id") DEFERRABLE INITIALLY DEFERRED,
ADD COLUMN "target_object_id" integer;
- ENHANCEMENT: switched over to use
django.utils.timezone.nowinstead ofdatetime.datetime.now
- FEATURE: added ability to override filtering of responses
- ENHANCEMENT: Made admin a bit more usable
- ENHANCEMENT: Rewrote
referral_responsesto run on Django 1.3
- ENHANCEMENT: send full context to the
create_referraltemplate tag
- BUG: Fixed a stupid mistake
- BUG: fixed an issue with sessions in Django 1.4
- FEATURE: added ability to label referrals
- ENHANCEMENT: added support for bootstrap-ajax.js
- FEATURE: added a
referral_responsesassignment template tag - FEATURE: added an
activity_displaytemplate filter - ENHANCEMENT: added a new classmethod on
Referralfor getting a referral object for a givenrequestobject.
ALTER TABLE "anafero_referral" ADD COLUMN "label" varchar(100) NOT NULL DEFAULT '';
- FEATURE: Add ability to define code generators external to anafero
- ENHANCEMENT: Add ability to pass in a user to
referral.respondin cases whenrequest.userisn't set yet (e.g. during signup) - FEATURE: Add ability to get a referral object from a request
- FEATURE: changed user on Referral to be nullable, thus enabling anonymous or site administered referral codes
- BUG: fixed target not being set in the
create_referralajax view
- DOC: fixed a typo in the docs
- ENHANCEMENT: added a response count property
- ENHANCEMENT: added the return of the referral code along with the URL in the
ajax reponse of
create_referral - BUG: added the return of the proper mimetype in the
create_referralajax view - ENHANCEMENT: moved the building of the URL for the referral code to a property on the Referral model
- FEATURE: added the ability to control referral code generation properties via settings at the site level
- BUG: fixed the url generation to include https if the site is configured to run SSL
- BUG: only delete cookies if user is present
- BUG: make sure to set a session value to prevent session key from changing with each request
- initial release
This project was originally named anafero and was created by the team at Eldarion. It was later donated to Pinax and at that time renamed to
pinax-referrals.
Contributing information can be found in the Pinax community health file repo.
In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a Code of Conduct. We ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you.
For updates and news regarding the Pinax Project, please follow us on Twitter @pinaxproject and check out our Pinax Project blog.
Copyright (c) 2012-present James Tauber and contributors under the MIT license.