combo/combo/apps/lingo/models.py

346 lines
12 KiB
Python
Raw Normal View History

2015-02-08 15:01:34 +01:00
# lingo - basket and payment system
# Copyright (C) 2015 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2015-03-06 12:04:41 +01:00
import datetime
import json
import requests
import urllib
from dateutil import parser
2015-03-06 12:04:41 +01:00
2015-02-08 15:01:34 +01:00
import eopayment
from jsonfield import JSONField
from django import template
2015-02-08 15:34:49 +01:00
from django.conf import settings
from django.db import models
from django.forms import models as model_forms, Select
2015-02-08 15:01:34 +01:00
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
2015-09-03 16:03:44 +02:00
from django.core.exceptions import PermissionDenied
2015-02-08 15:01:34 +01:00
from ckeditor.fields import RichTextField
from combo.data.models import CellBase
from combo.data.library import register_cell_class
from combo.utils import NothingInCacheException, sign_url
2015-02-08 15:01:34 +01:00
SERVICES = [
(eopayment.DUMMY, _('Dummy (for tests)')),
(eopayment.SYSTEMPAY, 'systempay (Banque Populaire)'),
(eopayment.SIPS, 'SIPS'),
(eopayment.SPPLUS, _('SP+ (Caisse d\'epargne)')),
(eopayment.OGONE, _('Ingenico (formerly Ogone)')),
(eopayment.PAYBOX, _('Paybox')),
2015-02-08 15:01:34 +01:00
]
def build_remote_item(data, regie):
return RemoteItem(id=data.get('id'), regie=regie,
creation_date=data['created'],
display_id=data['display_id'],
total_amount=data.get('total_amount'),
amount=data.get('amount'),
2015-09-03 16:03:44 +02:00
subject=data.get('label'),
has_pdf=data.get('has_pdf'))
2015-02-08 15:01:34 +01:00
class Regie(models.Model):
label = models.CharField(verbose_name=_('Label'), max_length=64)
slug = models.SlugField(unique=True)
description = models.TextField(verbose_name=_('Description'))
service = models.CharField(verbose_name=_('Payment Service'),
max_length=64, choices=SERVICES)
service_options = JSONField(blank=True,
verbose_name=_('Payment Service Options'))
webservice_url = models.URLField(_('Webservice URL to retrieve remote items'),
blank=True)
def is_remote(self):
return self.webservice_url != ''
2015-02-08 15:01:34 +01:00
class Meta:
verbose_name = _('Regie')
def natural_key(self):
return (self.slug,)
2015-02-08 15:01:34 +01:00
def __unicode__(self):
return self.label
def get_past_items(self, context):
"""
returns past items
"""
return self.get_items(context, past=True)
def get_items(self, context, past=False):
"""
returns current or past items
"""
if not self.is_remote():
payed = not past
return self.basketitem_set.filter(payment_date__isnull=payed,
user=context.get('user'))
if context.get('user'):
if context.get('request') and hasattr(context['request'], 'session') and \
context['request'].session.get('mellon_session'):
mellon = context.get('request').session['mellon_session']
url = self.webservice_url + '/invoices/'
if past:
url += 'history'
items = self.get_url(context['request'], url,
NameID=mellon['name_id_content']).json()
if items.get('data'):
return [build_remote_item(item, self) for item in items.get('data')]
return []
2015-09-03 16:03:44 +02:00
def download_item(self, request, item_id):
"""
downloads item's file
"""
if self.is_remote():
if hasattr(request, 'session') and request.session.get('mellon_session'):
mellon = request.session.get('mellon_session')
url = self.webservice_url + '/invoice/%s/pdf' % item_id
return self.get_url(request, url, NameID=mellon['name_id_content'])
raise PermissionDenied
def as_api_dict(self):
return {'slug': self.slug,
'label': self.label,
'description': self.description}
2015-02-08 15:34:49 +01:00
def get_url(self, request, url, **params):
orig = request.get_host()
url += '?orig=' + orig +'&' + urllib.urlencode(params)
signature_key = settings.LINGO_SIGNATURE_KEY
url = sign_url(url, key=signature_key)
return requests.get(url)
2015-02-08 15:34:49 +01:00
class BasketItem(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
regie = models.ForeignKey(Regie)
subject = models.CharField(verbose_name=_('Subject'), max_length=64)
source_url = models.URLField(_('Source URL'))
details = models.TextField(verbose_name=_('Details'), blank=True)
amount = models.DecimalField(verbose_name=_('Amount'),
decimal_places=2, max_digits=8)
creation_date = models.DateTimeField(auto_now_add=True)
cancellation_date = models.DateTimeField(null=True)
payment_date = models.DateTimeField(null=True)
2015-03-06 12:04:41 +01:00
notification_date = models.DateTimeField(null=True)
def notify(self):
# TODO: sign with real values
url = self.source_url + 'jump/trigger/paid?email=trigger@localhost&orig=combo'
url = sign_url(url, key='xxx')
message = {'result': 'ok'}
r = requests.post(url, data=json.dumps(message), timeout=3)
self.notification_date = timezone.now()
2015-03-06 12:04:41 +01:00
self.save()
class RemoteItem(object):
def __init__(self, id, regie, creation_date, total_amount,
2015-09-03 16:03:44 +02:00
amount, display_id, subject, has_pdf):
self.id = id
self.regie = regie
self.creation_date = parser.parse(creation_date)
self.total_amount = total_amount
self.display_id = display_id
self.amount = amount
self.subject = subject
2015-09-03 16:03:44 +02:00
self.has_pdf = has_pdf
2015-03-05 17:02:52 +01:00
class Transaction(models.Model):
items = models.ManyToManyField(BasketItem, blank=True)
start_date = models.DateTimeField(auto_now_add=True)
end_date = models.DateTimeField(null=True)
bank_data = JSONField(blank=True)
order_id = models.CharField(max_length=200)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
status = models.IntegerField(null=True)
def is_paid(self):
return self.status == eopayment.PAID
def get_status_label(self):
return {
0: _('Running'),
eopayment.PAID: _('Paid'),
eopayment.CANCELLED: _('Cancelled'),
}.get(self.status) or _('Unknown')
2015-03-05 17:02:52 +01:00
@register_cell_class
class LingoBasketCell(CellBase):
class Meta:
verbose_name = _('Basket')
@classmethod
def is_enabled(cls):
return Regie.objects.count() > 0
def is_relevant(self, context):
if not (getattr(context['request'], 'user', None) and context['request'].user.is_authenticated()):
return False
items = BasketItem.objects.filter(
user=context['request'].user, payment_date__isnull=True
).exclude(cancellation_date__isnull=False)
return len(items) > 0
def render(self, context):
basket_template = template.loader.get_template('lingo/combo/basket.html')
context['items'] = BasketItem.objects.filter(
user=context['request'].user, payment_date__isnull=True
).exclude(cancellation_date__isnull=False)
context['total'] = sum([x.amount for x in context['items']])
return basket_template.render(context)
@register_cell_class
class LingoRecentTransactionsCell(CellBase):
class Meta:
verbose_name = _('Recent Transactions')
@classmethod
def is_enabled(cls):
return Regie.objects.count() > 0
def is_relevant(self, context):
if not (getattr(context['request'], 'user', None) and context['request'].user.is_authenticated()):
return False
transactions = Transaction.objects.filter(
user=context['request'].user,
start_date__gte=timezone.now()-datetime.timedelta(days=7))
return len(transactions) > 0
def render(self, context):
recent_transactions_template = template.loader.get_template(
'lingo/combo/recent_transactions.html')
context['transactions'] = Transaction.objects.filter(
user=context['request'].user,
start_date__gte=timezone.now()-datetime.timedelta(days=7)
).order_by('-start_date')
return recent_transactions_template.render(context)
@register_cell_class
class LingoBasketLinkCell(CellBase):
user_dependant = True
class Meta:
verbose_name = _('Basket Link')
@classmethod
def is_enabled(cls):
return Regie.objects.count() > 0
def is_relevant(self, context):
if not (getattr(context['request'], 'user', None) and context['request'].user.is_authenticated()):
return False
items = BasketItem.objects.filter(
user=context['request'].user, payment_date__isnull=True
).exclude(cancellation_date__isnull=False)
return len(items) > 0
def render(self, context):
basket_template = template.loader.get_template('lingo/combo/basket_link.html')
context['items'] = BasketItem.objects.filter(
user=context['request'].user, payment_date__isnull=True
).exclude(cancellation_date__isnull=False)
context['total'] = sum([x.amount for x in context['items']])
return basket_template.render(context)
class Items(CellBase):
regie = models.CharField(_('Regie'), max_length=50, blank=True)
title = RichTextField(_('Title'), blank=True)
user_dependant = True
template_name = 'lingo/combo/items.html'
class Meta:
abstract = True
class Media:
js = ('xstatic/jquery-ui.min.js', 'js/gadjo.js',)
css = {'all': ('xstatic/themes/smoothness/jquery-ui.min.css', )}
@classmethod
def is_enabled(cls):
return Regie.objects.count() > 0
def get_default_form_class(self):
if Regie.objects.count() == 1:
return None
regies = [('', _('All'))]
regies.extend([(r.slug, r.label) for r in Regie.objects.all()])
return model_forms.modelform_factory(self.__class__,
fields=['regie', 'title'],
widgets={'regie': Select(choices=regies)})
def get_regies(self):
if self.regie:
return [Regie.objects.get(slug=self.regie)]
return Regie.objects.all()
def get_cell_extra_context(self):
ctx = {'title': self.title}
items = self.get_items()
# sort items by creation date
items.sort(key=lambda i: i.creation_date, reverse=True)
ctx.update({'items': items})
return ctx
def render(self, context):
self.context = context
if not context.get('synchronous'):
raise NothingInCacheException()
return super(Items, self).render(context)
@register_cell_class
class ItemsHistory(Items):
class Meta:
verbose_name = _('Items History Cell')
def get_items(self):
items = []
for r in self.get_regies():
items.extend(r.get_past_items(self.context))
return items
@register_cell_class
class ActiveItems(Items):
class Meta:
verbose_name = _('Active Items Cell')
def get_items(self):
items = []
for r in self.get_regies():
items.extend(r.get_items(self.context))
return items