Skip to content

Instantly share code, notes, and snippets.

@michaelhjulskov
Last active August 29, 2015 14:05
Show Gist options
  • Save michaelhjulskov/36aadfbd87213f3019ee to your computer and use it in GitHub Desktop.
Save michaelhjulskov/36aadfbd87213f3019ee to your computer and use it in GitHub Desktop.
Django Reminder Service app - for webshops to remind user to buy again
from django import forms
from oscar.core.loading import get_model
from django.utils.translation import ugettext_lazy as _
Reminder = get_model('reminder', 'Reminder')
class ReminderForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(ReminderForm, self).__init__(*args, **kwargs)
self.instance.user = user
class Meta:
model = Reminder
fields = ('num_days_after_latest_order',)
import hashlib
import random
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from datetime import timedelta, datetime
from django.contrib.sites.models import Site
from django.core import mail
# this example is made to work wit the ecommerse system "Django Oscar"
from oscar.core.compat import AUTH_USER_MODEL
from oscar.core.loading import get_model
from fancy_functions import get_users_latest_order, get_display
Order = get_model('order', 'Order')
Wishlist = get_model('wishlists', 'Wishlist')
class Reminder(models.Model):
"""
A reminder service - only available for authenticated users
send out a reminder email
either monthly or a number of days after latest order
either to re-buy the latest order, or to buy items from a favourite list
"""
# A user is required
#user = models.ForeignKey(AUTH_USER_MODEL, db_index=True, related_name="reminders", verbose_name=_('User'))
user = models.OneToOneField(AUTH_USER_MODEL, related_name="reminder", verbose_name=_('User'))
# days after latest order (min=7)
# num_days_after_latest_order = models.PositiveIntegerField(_("Number of days after latest order"), default=0)
NUM_DAYS_CHOICES = (
(0, _("no reminder, thanks")),
#(0, _("stop sending reminders")),
#(0, _("choose how long")),
(14, _("%d weeks") % 2),
(21, _("%d weeks") % 3),
(28, _("%d weeks") % 4),
(35, _("%d weeks") % 5),
(42, _("%d weeks") % 6),
(60, _("%d months") % 2),
(90, _("%d months") % 3),
(120, _("%d months") % 4),
(150, _("%d months") % 5),
(180, _("%d months") % 6),
)
num_days_after_latest_order = models.PositiveIntegerField(_("How long after latest order do you want to be reminded?"),
default=0, choices=NUM_DAYS_CHOICES)
# remind on an exact date in future
# exact_date = models.DateTimeField(_('OR if you want an exact date'), blank=True, null=True)
# This key are used to confirm and cancel alerts without logging in
# unsubscription key are generated when an instance is saved for
# the first time and can be used to confirm and unsubscribe the
# notifications.
key = models.CharField(_("Key"), max_length=128, blank=True, db_index=True)
# An alert can have two different statuses ``ACTIVE`` and ``INACTIVE``
ACTIVE, INACTIVE = ('Active', 'Inactive')
STATUS_CHOICES = (
(ACTIVE, _('Active')),
(INACTIVE, _('Inactive')),
)
status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default=ACTIVE)
date_latest_reminder = models.DateTimeField(_("Date latest reminder sent"), blank=True, null=True)
latest_reminder_order_number = models.PositiveIntegerField(_("Latest order number"), default=0)
date_created = models.DateTimeField(_("Date created"), auto_now_add=True)
date_inactivated = models.DateTimeField(_("Date cancelled"), blank=True, null=True)
"""
# buy same as an order from the order history
# buy products from a favourite list
# or both
HISTORY, FAVOURITE, BOTH = ('history', 'favourite', 'both')
METHOD_CHOICES = (
(HISTORY, _('same as an order from the order history')),
(FAVOURITE, _('products from a favourite list')),
(BOTH, _('both')),
)
method = models.CharField(_("Status"), max_length=20, choices=METHOD_CHOICES, default=BOTH)
"""
@property
def can_be_inactivated(self):
return self.status == self.ACTIVE
def inactivate(self):
self.status = self.INACTIVE
self.date_inactivated = datetime.now()
self.save()
inactivate.alters_data = True
@property
def can_be_activated(self):
return self.status == self.ACTIVE
def activate(self):
self.status = self.ACTIVE
self.date_inactivated = None
self.save()
inactivate.alters_data = True
@property
def is_inactive(self):
return self.status == self.INACTIVE
@property
def is_active(self):
return self.status == self.ACTIVE
def get_email_address(self):
return self.user.email
@property
def its_time_to_send_a_reminder(self):
if self.is_active:
latest_order = get_users_latest_order(self.user)
if latest_order:
latest_order_number = int(latest_order.number)
# we always send the reminder approx 7 days before
num_days_add = self.num_days_after_latest_order - 7
if self.latest_reminder_order_number < latest_order_number and latest_order.date_placed + timedelta(days=num_days_add) < datetime.now():
return True
return False
def generate_and_send_reminder_message(self):
latest_order = get_users_latest_order(self.user)
if latest_order and self.its_time_to_send_a_reminder:
site = Site.objects.get_current()
subject = _("Reminder from %(site_name)s - It's time to re-order") % {
"site_name":site.name
}
context = {
'reminder': self,
'latest_order': latest_order,
'site': site,
}
html_content = render_to_string("reminder/emails/reminder_message.html", context)
text_content = render_to_string("reminder/emails/reminder_message.txt", context)
msg = mail.EmailMultiAlternatives(subject, text_content, settings.OSCAR_FROM_EMAIL, [self.user.email])
msg.attach_alternative(html_content, "text/html")
msg.send()
self.date_latest_reminder = datetime.now()
self.latest_reminder_order_number = int(latest_order.number)
self.save()
generate_and_send_reminder_message.alters_data = True
def get_random_key(self):
"""
Get a random generated key based on SHA-1 and email address
"""
salt = hashlib.sha1(str(random.random()).encode('utf8')).hexdigest()
return hashlib.sha1((salt + self.email).encode('utf8')).hexdigest()
def get_cancel_url(self):
return reverse('reminder:cancel-by-key', kwargs={'key':self.key})
def get_update_url(self):
return reverse('reminder:reminder-update', kwargs={'pk':self.pk})
def get_absolute_url(self):
return reverse('reminder:reminder-list')
@property
def status_verbose(self):
return get_display(self.status, self.STATUS_CHOICES)
@property
def num_weeks_or_months(self):
return get_display(self.num_days_after_latest_order, self.NUM_DAYS_CHOICES)
def save(self, *args, **kwargs):
if not self.id:
self.key = self.get_random_key()
# Ensure date fields get updated when saving from modelform (which just
# calls save, and doesn't call the methods inactivate(), confirm() etc).
#if customer update days, should we reset any date or ordernumber, so that we will send a new reminder. e.g. if user get a reminder and he/she want to get it in 2 weeks again. they might change from 14 days to 28 days, and that should trigger a new reminder to be sent in approx 14 days
if self.status == self.INACTIVE and self.date_inactivated is None:
self.date_inactivated = datetime.now()
if self.status == self.ACTIVE:
self.date_inactivated = None
return super(Reminder, self).save(*args, **kwargs)
class Meta:
abstract = True
ordering = ['-status', 'date_created']
verbose_name = _("Reminder")
verbose_name_plural = _("Reminders")
def __unicode__(self):
return u"%s is reminded %s days after latest order (%s)" % (self.user.get_full_name, self.num_days_after_latest_order, self.status_verbose)
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from django import template
register = template.Library()
from reminder.forms import ReminderForm
# to be used like this in templates
# {% if request.user.is_authenticated %}{% user|as_reminder_dropdown %}{% endif %}
@register.filter
def as_reminder_dropdown(user):
if user:
num_days_after_latest_order = 0
initial_first_choice = _("no reminder, thanks")
#initial_first_choice = _("choose how long")
if user.reminder.exist():
num_days_after_latest_order = user.reminder.num_days_after_latest_order
if num_days_after_latest_order > 0:
initial_first_choice = _("stop sending reminders")
form = ReminderForm(
user = user,
initial={
"num_days_after_latest_order": num_days_after_latest_order,
}
)
# override the zero/first choice
form.fields['num_days_after_latest_order'].choices[0] = initial_first_choice
return render_to_string("partials/form_fields_compact_inline.html", {
'form': form
})
return ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment