Last active
August 29, 2015 14:05
-
-
Save michaelhjulskov/36aadfbd87213f3019ee to your computer and use it in GitHub Desktop.
Django Reminder Service app - for webshops to remind user to buy again
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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',) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment