You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

abstract_models.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import hashlib
  2. import random
  3. from django.db import models
  4. from django.utils.translation import ugettext_lazy as _
  5. from django.template import Template, Context, TemplateDoesNotExist
  6. from django.template.loader import get_template
  7. from django.conf import settings
  8. from django.utils.timezone import now
  9. from django.core.urlresolvers import reverse
  10. from oscar.apps.customer.managers import CommunicationTypeManager
  11. class AbstractEmail(models.Model):
  12. """
  13. This is a record of all emails sent to a customer.
  14. Normally, we only record order-related emails.
  15. """
  16. user = models.ForeignKey('auth.User', related_name='emails', verbose_name=_("User"))
  17. subject = models.TextField(_('Subject'), max_length=255)
  18. body_text = models.TextField(_("Body Text"))
  19. body_html = models.TextField(_("Body HTML"), blank=True, null=True)
  20. date_sent = models.DateTimeField(_("Date Sent"), auto_now_add=True)
  21. class Meta:
  22. abstract = True
  23. verbose_name = _('Email')
  24. verbose_name_plural = _('Emails')
  25. def __unicode__(self):
  26. return _("Email to %(user)s with subject '%(subject)s'") % {
  27. 'user': self.user.username, 'subject': self.subject}
  28. class AbstractCommunicationEventType(models.Model):
  29. """
  30. A 'type' of communication. Like a order confirmation email.
  31. """
  32. # Code used for looking up this event programmatically.
  33. # eg. PASSWORD_RESET
  34. code = models.SlugField(_('Code'), max_length=128)
  35. # Name is the friendly description of an event for use in the admin
  36. name = models.CharField(
  37. _('Name'), max_length=255,
  38. help_text=_("This is just used for organisational purposes"))
  39. # We allow communication types to be categorised
  40. ORDER_RELATED = _('Order related')
  41. USER_RELATED = _('User related')
  42. category = models.CharField(_('Category'), max_length=255,
  43. default=ORDER_RELATED)
  44. # Template content for emails
  45. email_subject_template = models.CharField(
  46. _('Email Subject Template'), max_length=255, blank=True, null=True)
  47. email_body_template = models.TextField(
  48. _('Email Body Template'), blank=True, null=True)
  49. email_body_html_template = models.TextField(
  50. _('Email Body HTML Template'), blank=True, null=True,
  51. help_text=_("HTML template"))
  52. # Template content for SMS messages
  53. sms_template = models.CharField(_('SMS Template'), max_length=170,
  54. blank=True, help_text=_("SMS template"))
  55. date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
  56. date_updated = models.DateTimeField(_("Date Updated"), auto_now=True)
  57. objects = CommunicationTypeManager()
  58. # File templates
  59. email_subject_template_file = 'customer/emails/commtype_%s_subject.txt'
  60. email_body_template_file = 'customer/emails/commtype_%s_body.txt'
  61. email_body_html_template_file = 'customer/emails/commtype_%s_body.html'
  62. sms_template_file = 'customer/sms/commtype_%s_body.txt'
  63. class Meta:
  64. abstract = True
  65. verbose_name = _("Communication event type")
  66. verbose_name_plural = _("Communication event types")
  67. def get_messages(self, ctx=None):
  68. """
  69. Return a dict of templates with the context merged in
  70. We look first at the field templates but fail over to
  71. a set of file templates that follow a conventional path.
  72. """
  73. code = self.code.lower()
  74. # Build a dict of message name to Template instances
  75. templates = {'subject': 'email_subject_template',
  76. 'body': 'email_body_template',
  77. 'html': 'email_body_html_template',
  78. 'sms': 'sms_template'}
  79. for name, attr_name in templates.items():
  80. field = getattr(self, attr_name, None)
  81. if field is not None:
  82. # Template content is in a model field
  83. templates[name] = Template(field)
  84. else:
  85. # Model field is empty - look for a file template
  86. template_name = getattr(self, "%s_file" % attr_name) % code
  87. try:
  88. templates[name] = get_template(template_name)
  89. except TemplateDoesNotExist:
  90. templates[name] = None
  91. # Pass base URL for serving images within HTML emails
  92. if ctx is None:
  93. ctx = {}
  94. ctx['static_base_url'] = getattr(settings,
  95. 'OSCAR_STATIC_BASE_URL', None)
  96. messages = {}
  97. for name, template in templates.items():
  98. messages[name] = template.render(Context(ctx)) if template else ''
  99. # Ensure the email subject doesn't contain any newlines
  100. messages['subject'] = messages['subject'].replace("\n", "")
  101. return messages
  102. def __unicode__(self):
  103. return self.name
  104. def is_order_related(self):
  105. return self.category == self.ORDER_RELATED
  106. def is_user_related(self):
  107. return self.category == self.USER_RELATED
  108. class AbstractNotification(models.Model):
  109. recipient = models.ForeignKey('auth.User', related_name='notifications',
  110. db_index=True)
  111. # Not all notifications will have a sender.
  112. sender = models.ForeignKey('auth.User', null=True)
  113. # HTML is allowed in this field as it can contain links
  114. subject = models.CharField(max_length=255)
  115. body = models.TextField()
  116. # Some projects may want to categorise their notifications. You may want
  117. # to use this field to show a different icons next to the notification.
  118. category = models.CharField(max_length=255, null=True)
  119. INBOX, ARCHIVE = 'Inbox', 'Archive'
  120. choices = (
  121. (INBOX, _(INBOX)),
  122. (ARCHIVE, _(ARCHIVE)))
  123. location = models.CharField(max_length=32, choices=choices,
  124. default=INBOX)
  125. date_sent = models.DateTimeField(auto_now_add=True)
  126. date_read = models.DateTimeField(null=True)
  127. class Meta:
  128. ordering = ('-date_sent',)
  129. abstract = True
  130. def __unicode__(self):
  131. return self.subject
  132. def archive(self):
  133. self.location = self.ARCHIVE
  134. self.save()
  135. archive.alters_data = True
  136. @property
  137. def is_read(self):
  138. return self.date_read is not None
  139. class AbstractProductAlert(models.Model):
  140. """
  141. An alert for when a product comes back in stock
  142. """
  143. product = models.ForeignKey('catalogue.Product')
  144. # A user is only required if the notification is created by a
  145. # registered user, anonymous users will only have an email address
  146. # attached to the notification
  147. user = models.ForeignKey('auth.User', db_index=True, blank=True, null=True,
  148. related_name="alerts", verbose_name=_('User'))
  149. email = models.EmailField(_("Email"), db_index=True, blank=True, null=True)
  150. # This key are used to confirm and cancel alerts for anon users
  151. key = models.CharField(_("Key"), max_length=128, null=True, db_index=True)
  152. # An alert can have two different statuses for authenticated
  153. # users ``ACTIVE`` and ``INACTIVE`` and anonymous users have an
  154. # additional status ``UNCONFIRMED``. For anonymous users a confirmation
  155. # and unsubscription key are generated when an instance is saved for
  156. # the first time and can be used to confirm and unsubscribe the
  157. # notifications.
  158. UNCONFIRMED, ACTIVE, CANCELLED, CLOSED = (
  159. 'Unconfirmed', 'Active', 'Cancelled', 'Closed')
  160. STATUS_CHOICES = (
  161. (UNCONFIRMED, _('Not yet confirmed')),
  162. (ACTIVE, _('Active')),
  163. (CANCELLED, _('Cancelled')),
  164. (CLOSED, _('Closed')),
  165. )
  166. status = models.CharField(_("Status"), max_length=20,
  167. choices=STATUS_CHOICES, default=ACTIVE)
  168. date_created = models.DateTimeField(_("Date created"), auto_now_add=True)
  169. date_confirmed = models.DateTimeField(_("Date confirmed"), blank=True,
  170. null=True)
  171. date_cancelled = models.DateTimeField(_("Date cancelled"), blank=True,
  172. null=True)
  173. date_closed = models.DateTimeField(_("Date closed"), blank=True, null=True)
  174. class Meta:
  175. abstract = True
  176. @property
  177. def is_anonymous(self):
  178. return self.user is None
  179. @property
  180. def can_be_confirmed(self):
  181. return self.status == self.UNCONFIRMED
  182. @property
  183. def can_be_cancelled(self):
  184. return self.status == self.ACTIVE
  185. @property
  186. def is_cancelled(self):
  187. return self.status == self.CANCELLED
  188. @property
  189. def is_active(self):
  190. return self.status == self.ACTIVE
  191. def confirm(self):
  192. self.status = self.ACTIVE
  193. self.date_confirmed = now()
  194. self.save()
  195. confirm.alters_data = True
  196. def cancel(self):
  197. self.status = self.CANCELLED
  198. self.date_cancelled = now()
  199. self.save()
  200. cancel.alters_data = True
  201. def close(self):
  202. self.status = self.CLOSED
  203. self.date_closed = now()
  204. self.save()
  205. close.alters_data = True
  206. def get_email_address(self):
  207. if self.user:
  208. return self.user.email
  209. else:
  210. return self.email
  211. def save(self, *args, **kwargs):
  212. if not self.id and not self.user:
  213. self.key = self.get_random_key()
  214. self.status = self.UNCONFIRMED
  215. # Ensure date fields get updated when saving from modelform (which just
  216. # calls save, and doesn't call the methods cancel(), confirm() etc).
  217. if self.status == self.CANCELLED and self.date_cancelled is None:
  218. self.date_cancelled = now()
  219. if not self.user and self.status == self.ACTIVE and self.date_confirmed is None:
  220. self.date_confirmed = now()
  221. if self.status == self.CLOSED and self.date_closed is None:
  222. self.date_closed = now()
  223. return super(AbstractProductAlert, self).save(*args, **kwargs)
  224. def get_random_key(self):
  225. """
  226. Get a random generated key based on SHA-1 and email address
  227. """
  228. salt = hashlib.sha1(str(random.random())).hexdigest()
  229. return hashlib.sha1(salt + self.email).hexdigest()
  230. def get_confirm_url(self):
  231. return reverse('customer:alerts-confirm', kwargs={'key': self.key})
  232. def get_cancel_url(self):
  233. return reverse('customer:alerts-cancel', kwargs={'key': self.key})