<commit_before>#!env/python3
# coding: utf-8
import ipdb;

import os
import sys
import time
import requests
import logging
import json
import pylxd
import subprocess
import shutil
import uuid

from mongoengine import *
from celery import Celery, Task
from config import *
from framework import *

app = Celery('pirus_worker')
app.conf.update(
    BROKER_URL = 'amqp://guest@localhost',
    CELERY_RESULT_BACKEND = 'rpc',
    CELERY_RESULT_PERSISTENT = False,

    CELERY_TASK_SERIALIZER = 'json',
    CELERY_ACCEPT_CONTENT = ['json'],
    CELERY_RESULT_SERIALIZER = 'json',
    CELERY_INCLUDE = [
    'pirus_worker'
    ],
    CELERY_TIMEZONE = 'Europe/Paris',
    CELERY_ENABLE_UTC = True,
)

lxd_client = pylxd.Client()

def execute(cmd, olog=None, elog=None):
    subprocess.call(cmd)
    # with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
    #     # TODO FIXME => logging realtime not working :( ...
    #     out = str(proc.stdout.read())
    #     err = str(proc.stderr.read())
    #     if out != "":
    #         olog.info(out)
    #     if err != "":
    #         elog.info(err)

class PirusTask(Task):
    """Task that sends notification on completion."""
    abstract = True

    notify_url = ""
    run_path   = ""

    def after_return(self, status, retval, task_id, args, kwargs, einfo):
        #data = {'clientid': kwargs['clientid'], 'result': retval}
        #requests.get(NOTIFY_URL, data=data)
        pass

    def dump_context(self):
        print('  Context : Executing task id {0.id}, args: {0.args!r} kwargs: {0.kwargs!r}'.format(self.request))

    def notify_status(self, status:str):
        requests.post(self.notify_url, data = '{"status": "'+status+'"}' )

    def error(self, msg:str, error_code:int=500):
        # TODO : some log ?...
        print(error_code, " : ", msg)
        self.notify_status('ERROR')
        return error_code

@app.task(base=PirusTask, queue='PirusQueue', bind=True)
def run_pipeline(self, run_id):
    from api_v1.model import Run, PirusFile, Pipeline
    connect('pirus')

    run = Run.from_id(run_id)
    if run is None :
        # TODO : log error
        return

    self.notify_url = run.notify_url
    print(self.notify_url)
    # # Init logs
    # print(os.path.join(lpath, "pirus.log"))
    # setup_logger('pirus_worker', os.path.join(lpath, "pirus_worker.log"))
    # setup_logger('run_out', os.path.join(lpath, "out.log"))
    # setup_logger('run_err', os.path.join(lpath, "err.log"))
    # wlog = logging.getLogger('pirus_worker')
    # # olog = logging.getLogger('run_out')
    # # elog = logging.getLogger('run_err')

    # wlog.info('INIT    | Pirus worker initialisation : ')
    # wlog.info('INIT    |  - LXD alias : ' + pipeline.lxd_alias)
    # wlog.info('INIT    |  - Run ID  : ' + self.run_private_id)
    # wlog.info('INIT    | Directory created : ')
    # wlog.info('INIT    |  - inputs  : ' + ipath)
    # wlog.info('INIT    |  - outputs : ' + opath)
    # wlog.info('INIT    |  - logs    : ' + lpath)
    # wlog.info('INIT    |  - db      : ' + DATABASES_DIR)
    # wlog.info('INIT    | Run config : ' + json.dumps(config))
    # wlog.info('INIT    | Run inputs : ' + json.dumps(inputs))

    self.notify_status("WAITING")

    # Check that all inputs files are ready to be used
    for file_id in run.inputs:
        f = PirusFile.from_id(file_id)
        if f is None :
            return self.error('Inputs file deleted before the start of the run. Run aborded.')
        if f.status not in ["CHECKED", "UPLOADED"]:
            # inputs not ready, we keep the run in the waiting status
            return 1

    # Inputs files ready to use, looking for lxd resources now
    # count = 0
    # for lxd_container in lxd_client.containers.all():
    #     if lxd_container.name.startswith(LXD_CONTAINER_PREFIX) and lxd_container.status == 'Running':
    #         count += 1
    count = len(Run.objects(status="RUNNING")) + len(Run.objects(status="INITIALIZING")) + len(Run.objects(status="FINISHING"))
    if len(Run.objects(status="RUNNING")) >= LXD_MAX:
        # too many run in progress, we keep the run in the waiting status
        return 1

    self.notify_status("INITIALIZING")

    #LXD ready ! Prepare filesystem of the server to host lxc container files
    root_path    = os.path.join(RUNS_DIR, run.lxd_container)
    inputs_path  = os.path.join(root_path, "inputs")
    outputs_path = os.path.join(root_path, "outputs")
    logs_path    = os.path.join(root_path, "logs")

    # Init directories
    if not os.path.exists(inputs_path):
        os.makedirs(inputs_path)
    if not os.path.exists(outputs_path):
        os.makedirs(outputs_path)
        os.chmod(outputs_path, 0o777)
    if not os.path.exists(logs_path):
        os.makedirs(logs_path)
        os.chmod(logs_path, 0o777)

    # Put inputs files in the inputs directory of the run
    conf_file = os.path.join(inputs_path, "config.json")
    data = json.loads(run.config)
    with open(conf_file, 'w') as f:
        f.write(json.dumps(data, sort_keys=True, indent=4))
        os.chmod(conf_file, 0o777)

    for file_id in run.inputs:
        f = PirusFile.from_id(file_id)
        #os.symlink(f.path, os.path.join(inputs_path, f.name))
        link_path = os.path.join(inputs_path, f.name)
        os.link(f.path, link_path)
        os.chmod(link_path, 0o644)

    # Setting up the lxc container for the run
    try:
        pipeline = Pipeline.from_id(run.pipeline_id)
        # create run file
        run_file = os.path.join(root_path, "start_" + run.lxd_container + ".sh")
        print(run_file)
        with open(run_file, 'w') as f:
            f.write("#!/bin/bash\n")
            f.write(pipeline.lxd_run_cmd + " 1> " + os.path.join(pipeline.lxd_logs_path, 'out.log') + " 2> " + os.path.join(pipeline.lxd_logs_path, "err.log\n"))
            f.write("curl -X POST -d '{\"status\" : \"FINISHING\"}' " + run.notify_url + "\n")
            os.chmod(run_file, 0o777)

        # create container
        subprocess.call(["lxc", "init", run.lxd_image, run.lxd_container])
        # set up env
        subprocess.call(["lxc", "config", "set", run.lxd_container, "environment.PIRUS_NOTIFY_URL", self.notify_url ])
        subprocess.call(["lxc", "config", "set", run.lxd_container, "environment.PIRUS_CONFIG_FILE", os.path.join(pipeline.lxd_inputs_path, "config.json") ])
        # set up devices
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_inputs",  "disk", "source="+inputs_path,   "path=" + pipeline.lxd_inputs_path[1:], "readonly=True"])
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_outputs", "disk", "source="+outputs_path,  "path=" + pipeline.lxd_outputs_path[1:]])
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_logs",    "disk", "source="+logs_path,     "path=" + pipeline.lxd_logs_path[1:]])
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_db",      "disk", "source="+DATABASES_DIR, "path=" + pipeline.lxd_db_path[1:], "readonly=True"])
    except:
        return self.error('Unexpected error ' + str(sys.exc_info()[0]))
        raise

    # Run the pipe !
    try:
        subprocess.call(["lxc", "start", run.lxd_container])
        lxd_run_file = os.path.join("/", os.path.basename(run_file))
        subprocess.call(["lxc", "file", "push", run_file, run.lxd_container + lxd_run_file])
        # for file_id in run.inputs:
        #     f = PirusFile.from_id(file_id)
        #     print ("push " + f.path + " to " + run.lxd_container + os.path.join(pipeline.lxd_inputs_path, f.name))
        #     subprocess.call(["lxc", "file", "push", f.path, run.lxd_container + os.path.join(pipeline.lxd_inputs_path, f.name)])
        subprocess.call(["lxc", "exec", run.lxd_container, "chmod", "+x", lxd_run_file])
        subprocess.Popen(["lxc", "exec", run.lxd_container, lxd_run_file])
        self.notify_status("RUNNING")
    except:
        return self.error('Unexpected error ' + str(sys.exc_info()[0]))
        raise

@app.task(base=PirusTask, queue='PirusQueue', bind=True)
def terminate_run(self, run_id):
    # Init celery task

    from api_v1.model import Run, PirusFile, Pipeline
    connect('pirus')
    run = Run.from_id(run_id)
    if run is None :
        # TODO : log error
        return
    self.notify_url = run.notify_url

    # Stop container and clear resource
    try:
        # Clean outputs
        subprocess.call(["lxc", "exec", run.lxd_container, "--", "chmod", "755", "-Rf", "/pipeline"])
        subprocess.call(["lxc", "delete", run.lxd_container, "--force"])
    except:
        return self.error('Unexpected error ' + str(sys.exc_info()[0]))
        raise

    # Register outputs files
    root_path    = os.path.join(RUNS_DIR, run.lxd_container)
    outputs_path = os.path.join(root_path, "outputs")
    logs_path    = os.path.join(root_path, "logs")

    run.end = str(datetime.datetime.now().timestamp())

    print("Analyse", outputs_path)
    run.outputs = []
    for f in os.listdir(outputs_path):
        if os.path.isfile(s.path.join(outputs_path, f)):
            file_name = str(uuid.uuid4())
            file_path = os.path.join(FILES_DIR, file_name)
            print (" - Move : ", f, " ==> ", file_path)
            # 1- move file to FILE directory
            os.rename(os.path.join(outputs_path, f), file_path)
            # 2- create symlink
            os.link(file_path, os.path.join(outputs_path, f))

            # 3- register in db
            pirusfile = PirusFile()
            pirusfile.import_data({
                    "name"         : f,
                    "type"         : os.path.splitext(f)[1][1:].strip().lower(),
                    "path"         : file_path,
                    "size"         : os.path.getsize(file_path),
                    "status"       : "CHECKED",
                    "create_date"  : str(datetime.datetime.now().timestamp()),
                    "md5sum"       : md5(file_path),
                    "runs"         : [ str(run.id) ]
                })
            pirusfile.save()
            run.outputs.append(str(pirusfile.id))
        print (" - Not a file : ", f)

    run.save()
    self.notify_status("DONE")<commit_msg>fix finnishing run<commit_after>#!env/python3
# coding: utf-8
import ipdb;

import os
import sys
import time
import requests
import logging
import json
import pylxd
import subprocess
import shutil
import uuid

from mongoengine import *
from celery import Celery, Task
from config import *
from framework import *

app = Celery('pirus_worker')
app.conf.update(
    BROKER_URL = 'amqp://guest@localhost',
    CELERY_RESULT_BACKEND = 'rpc',
    CELERY_RESULT_PERSISTENT = False,

    CELERY_TASK_SERIALIZER = 'json',
    CELERY_ACCEPT_CONTENT = ['json'],
    CELERY_RESULT_SERIALIZER = 'json',
    CELERY_INCLUDE = [
    'pirus_worker'
    ],
    CELERY_TIMEZONE = 'Europe/Paris',
    CELERY_ENABLE_UTC = True,
)

lxd_client = pylxd.Client()

def execute(cmd, olog=None, elog=None):
    subprocess.call(cmd)
    # with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
    #     # TODO FIXME => logging realtime not working :( ...
    #     out = str(proc.stdout.read())
    #     err = str(proc.stderr.read())
    #     if out != "":
    #         olog.info(out)
    #     if err != "":
    #         elog.info(err)

class PirusTask(Task):
    """Task that sends notification on completion."""
    abstract = True

    notify_url = ""
    run_path   = ""

    def after_return(self, status, retval, task_id, args, kwargs, einfo):
        #data = {'clientid': kwargs['clientid'], 'result': retval}
        #requests.get(NOTIFY_URL, data=data)
        pass

    def dump_context(self):
        print('  Context : Executing task id {0.id}, args: {0.args!r} kwargs: {0.kwargs!r}'.format(self.request))

    def notify_status(self, status:str):
        requests.post(self.notify_url, data = '{"status": "'+status+'"}' )

    def error(self, msg:str, error_code:int=500):
        # TODO : some log ?...
        print(error_code, " : ", msg)
        self.notify_status('ERROR')
        return error_code

@app.task(base=PirusTask, queue='PirusQueue', bind=True)
def run_pipeline(self, run_id):
    from api_v1.model import Run, PirusFile, Pipeline
    connect('pirus')

    run = Run.from_id(run_id)
    if run is None :
        # TODO : log error
        return

    self.notify_url = run.notify_url
    print(self.notify_url)
    # # Init logs
    # print(os.path.join(lpath, "pirus.log"))
    # setup_logger('pirus_worker', os.path.join(lpath, "pirus_worker.log"))
    # setup_logger('run_out', os.path.join(lpath, "out.log"))
    # setup_logger('run_err', os.path.join(lpath, "err.log"))
    # wlog = logging.getLogger('pirus_worker')
    # # olog = logging.getLogger('run_out')
    # # elog = logging.getLogger('run_err')

    # wlog.info('INIT    | Pirus worker initialisation : ')
    # wlog.info('INIT    |  - LXD alias : ' + pipeline.lxd_alias)
    # wlog.info('INIT    |  - Run ID  : ' + self.run_private_id)
    # wlog.info('INIT    | Directory created : ')
    # wlog.info('INIT    |  - inputs  : ' + ipath)
    # wlog.info('INIT    |  - outputs : ' + opath)
    # wlog.info('INIT    |  - logs    : ' + lpath)
    # wlog.info('INIT    |  - db      : ' + DATABASES_DIR)
    # wlog.info('INIT    | Run config : ' + json.dumps(config))
    # wlog.info('INIT    | Run inputs : ' + json.dumps(inputs))

    self.notify_status("WAITING")

    # Check that all inputs files are ready to be used
    for file_id in run.inputs:
        f = PirusFile.from_id(file_id)
        if f is None :
            return self.error('Inputs file deleted before the start of the run. Run aborded.')
        if f.status not in ["CHECKED", "UPLOADED"]:
            # inputs not ready, we keep the run in the waiting status
            return 1

    # Inputs files ready to use, looking for lxd resources now
    # count = 0
    # for lxd_container in lxd_client.containers.all():
    #     if lxd_container.name.startswith(LXD_CONTAINER_PREFIX) and lxd_container.status == 'Running':
    #         count += 1
    count = len(Run.objects(status="RUNNING")) + len(Run.objects(status="INITIALIZING")) + len(Run.objects(status="FINISHING"))
    if len(Run.objects(status="RUNNING")) >= LXD_MAX:
        # too many run in progress, we keep the run in the waiting status
        return 1

    self.notify_status("INITIALIZING")

    #LXD ready ! Prepare filesystem of the server to host lxc container files
    root_path    = os.path.join(RUNS_DIR, run.lxd_container)
    inputs_path  = os.path.join(root_path, "inputs")
    outputs_path = os.path.join(root_path, "outputs")
    logs_path    = os.path.join(root_path, "logs")

    # Init directories
    if not os.path.exists(inputs_path):
        os.makedirs(inputs_path)
    if not os.path.exists(outputs_path):
        os.makedirs(outputs_path)
        os.chmod(outputs_path, 0o777)
    if not os.path.exists(logs_path):
        os.makedirs(logs_path)
        os.chmod(logs_path, 0o777)

    # Put inputs files in the inputs directory of the run
    conf_file = os.path.join(inputs_path, "config.json")
    data = json.loads(run.config)
    with open(conf_file, 'w') as f:
        f.write(json.dumps(data, sort_keys=True, indent=4))
        os.chmod(conf_file, 0o777)

    for file_id in run.inputs:
        f = PirusFile.from_id(file_id)
        link_path = os.path.join(inputs_path, f.name)
        os.link(f.path, link_path)
        os.chmod(link_path, 0o644)

    # Setting up the lxc container for the run
    try:
        pipeline = Pipeline.from_id(run.pipeline_id)
        # create run file
        run_file = os.path.join(root_path, "start_" + run.lxd_container + ".sh")
        print(run_file)
        with open(run_file, 'w') as f:
            f.write("#!/bin/bash\n")
            f.write(pipeline.lxd_run_cmd + " 1> " + os.path.join(pipeline.lxd_logs_path, 'out.log') + " 2> " + os.path.join(pipeline.lxd_logs_path, "err.log || curl -X POST -d '{\"status\" : \"ERROR\"}' " + run.notify_url + "\n"))
            f.write("curl -X POST -d '{\"status\" : \"FINISHING\"}' " + run.notify_url + "\n")
            os.chmod(run_file, 0o777)

        # create container
        subprocess.call(["lxc", "init", run.lxd_image, run.lxd_container])
        # set up env
        subprocess.call(["lxc", "config", "set", run.lxd_container, "environment.PIRUS_NOTIFY_URL", self.notify_url ])
        subprocess.call(["lxc", "config", "set", run.lxd_container, "environment.PIRUS_CONFIG_FILE", os.path.join(pipeline.lxd_inputs_path, "config.json") ])
        # set up devices
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_inputs",  "disk", "source="+inputs_path,   "path=" + pipeline.lxd_inputs_path[1:], "readonly=True"])
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_outputs", "disk", "source="+outputs_path,  "path=" + pipeline.lxd_outputs_path[1:]])
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_logs",    "disk", "source="+logs_path,     "path=" + pipeline.lxd_logs_path[1:]])
        subprocess.call(["lxc", "config", "device", "add", run.lxd_container, "pirus_db",      "disk", "source="+DATABASES_DIR, "path=" + pipeline.lxd_db_path[1:], "readonly=True"])
    except:
        return self.error('Unexpected error ' + str(sys.exc_info()[0]))
        raise

    # Run the pipe !
    try:
        subprocess.call(["lxc", "start", run.lxd_container])
        lxd_run_file = os.path.join("/", os.path.basename(run_file))
        subprocess.call(["lxc", "file", "push", run_file, run.lxd_container + lxd_run_file])
        # for file_id in run.inputs:
        #     f = PirusFile.from_id(file_id)
        #     print ("push " + f.path + " to " + run.lxd_container + os.path.join(pipeline.lxd_inputs_path, f.name))
        #     subprocess.call(["lxc", "file", "push", f.path, run.lxd_container + os.path.join(pipeline.lxd_inputs_path, f.name)])
        subprocess.call(["lxc", "exec", run.lxd_container, "chmod", "+x", lxd_run_file])
        subprocess.Popen(["lxc", "exec", run.lxd_container, lxd_run_file])
        self.notify_status("RUNNING")
    except:
        return self.error('Unexpected error ' + str(sys.exc_info()[0]))
        raise

@app.task(base=PirusTask, queue='PirusQueue', bind=True)
def terminate_run(self, run_id):
    # Init celery task

    from api_v1.model import Run, PirusFile, Pipeline
    connect('pirus')
    run = Run.from_id(run_id)
    if run is None :
        # TODO : log error
        return
    self.notify_url = run.notify_url

    # Stop container and clear resource
    try:
        # Clean outputs
        subprocess.call(["lxc", "exec", run.lxd_container, "--", "chmod", "755", "-Rf", "/pipeline"])
        subprocess.call(["lxc", "delete", run.lxd_container, "--force"])
    except:
        return self.error('Unexpected error ' + str(sys.exc_info()[0]))
        raise

    # Register outputs files
    root_path    = os.path.join(RUNS_DIR, run.lxd_container)
    outputs_path = os.path.join(root_path, "outputs")
    logs_path    = os.path.join(root_path, "logs")

    run.end = str(datetime.datetime.now().timestamp())

    print("Analyse", outputs_path)
    run.outputs = []
    for f in os.listdir(outputs_path):
        if os.path.isfile(os.path.join(outputs_path, f)):
            file_name = str(uuid.uuid4())
            file_path = os.path.join(FILES_DIR, file_name)
            print (" - Move : ", f, " ==> ", file_path)
            # 1- move file to FILE directory
            os.rename(os.path.join(outputs_path, f), file_path)
            # 2- create link
            # os.link(file_path, os.path.join(outputs_path, f))

            # 3- register in db
            pirusfile = PirusFile()
            pirusfile.import_data({
                    "name"         : f,
                    "type"         : os.path.splitext(f)[1][1:].strip().lower(),
                    "path"         : file_path,
                    "size"         : os.path.getsize(file_path),
                    "upload_offset": os.path.getsize(file_path),
                    "status"       : "CHECKED",
                    "create_date"  : str(datetime.datetime.now().timestamp()),
                    "md5sum"       : md5(file_path),
                    "runs"         : [ str(run.id) ],
                    "tags"         : [run.name, "result", ""]
                })
            pirusfile.save()
            run.outputs.append(str(pirusfile.id))

    run.save()
    self.notify_status("DONE")<|endoftext|>


<commit_before>import datetime
import json
import traceback

from django.conf import settings
from django.core.mail import EmailMessage
from django.db import models
from django.utils import timezone
from django.template.loader import render_to_string

from django.contrib.auth.models import User
from django.contrib.sites.models import Site

import stripe

from jsonfield.fields import JSONField

from payments.settings import PAYMENTS_PLANS, INVOICE_FROM_EMAIL
from payments.settings import plan_from_stripe_id
from payments.signals import WEBHOOK_SIGNALS
from payments.signals import purchase_made, webhook_processing_error

def convert_tstamp(response, field_name):
    try:
        if response[field_name]:
            return datetime.datetime.fromtimestamp(
                response[field_name],
                timezone.utc
            )
    except KeyError:
        pass
    return None

class StripeObject(models.Model):

    stripe_id = models.CharField(max_length=50, unique=True)
    created_at = models.DateTimeField(default=timezone.now)

    class Meta:
        abstract = True

class EventProcessingException(models.Model):

    event = models.ForeignKey("Event", null=True)
    data = models.TextField()
    message = models.CharField(max_length=500)
    traceback = models.TextField()
    created_at = models.DateTimeField(default=timezone.now)

    @classmethod
    def log(cls, data, exception, event):
        cls.objects.create(
            event=event,
            data=data or "",
            message=str(exception),
            traceback=traceback.format_exc()
        )

    def __unicode__(self):
        return u"<%s, pk=%s, Event=%s>" % (self.message, self.pk, self.event)

class Event(StripeObject):

    kind = models.CharField(max_length=250)
    livemode = models.BooleanField()
    customer = models.ForeignKey("Customer", null=True)
    webhook_message = JSONField()
    validated_message = JSONField(null=True)
    valid = models.NullBooleanField(null=True)
    processed = models.BooleanField(default=False)

    @property
    def message(self):
        return self.validated_message

    def __unicode__(self):
        return "%s - %s" % (self.kind, self.stripe_id)

    def link_customer(self):
        cus_id = None
        customer_crud_events = [
            "customer.created",
            "customer.updated",
            "customer.deleted"
        ]
        if self.kind in customer_crud_events:
            cus_id = self.message["data"]["object"]["id"]
        else:
            cus_id = self.message["data"]["object"]["customer"]

        if cus_id is not None:
            try:
                self.customer = Customer.objects.get(stripe_id=cus_id)
                self.save()
            except Customer.DoesNotExist:
                pass

    def validate(self):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        evt = stripe.Event.retrieve(self.stripe_id)
        self.validated_message = json.loads(
            json.dumps(
                evt.to_dict(),
                sort_keys=True,
                cls=stripe.StripeObjectEncoder
            )
        )
        if self.webhook_message["data"] == self.validated_message["data"]:
            self.valid = True
        else:
            self.valid = False
        self.save()

    def process(self):
        """
            "charge.succeeded",
            "charge.failed",
            "charge.refunded",
            "charge.updated",
            "charge.disputed",
            "customer.created",
            "customer.updated",
            "customer.deleted",
            "customer.subscription.created",
            "customer.subscription.updated",
            "customer.subscription.deleted",
            "customer.subscription.trial_will_end",
            "customer.discount.created",
            "customer.discount.updated",
            "customer.discount.deleted",
            "invoice.created",
            "invoice.updated",
            "invoice.payment_succeeded",
            "invoice.payment_failed",
            "invoiceitem.created",
            "invoiceitem.updated",
            "invoiceitem.deleted",
            "plan.created",
            "plan.updated",
            "plan.deleted",
            "coupon.created",
            "coupon.updated",
            "coupon.deleted",
            "transfer.created",
            "transfer.failed",
            "ping"
        """
        if self.valid and not self.processed:
            try:
                if not self.kind.startswith("plan."):
                    self.link_customer()
                if self.kind.startswith("invoice."):
                    Invoice.handle_event(self)
                elif self.kind.startswith("charge."):
                    self.customer.record_charge(self.message["data"]["object"])
                self.send_signal()
                self.processed = True
                self.save()
            except stripe.StripeError, e:
                EventProcessingException.log(
                    data=e.http_body,
                    exception=e,
                    event=self
                )
                webhook_processing_error.send(
                    sender=Event,
                    data=e.http_body,
                    exception=e
                )

    def send_signal(self):
        signal = WEBHOOK_SIGNALS.get(self.kind)
        if signal:
            return signal.send(sender=Event, event=self)

class Customer(StripeObject):

    user = models.OneToOneField(User)

    plan = models.CharField(max_length=100, blank=True)

    card_fingerprint = models.CharField(max_length=200, null=True)
    card_last_4 = models.CharField(max_length=4, null=True)
    card_kind = models.CharField(max_length=50, blank=True)

    def display_plan(self):
        if self.plan:
            return PAYMENTS_PLANS[self.plan]["name"]
        return ""

    def has_active_subscription(self):
        if not self.current_subscription:
            return False
        return self.current_subscription.is_valid()

    @property
    def current_subscription(self):
        if not hasattr(self, "_current_subscription"):
            try:
                self._current_subscription = self.subscriptions.exclude(
                    status__in=["past_due", "unpaid"]
                ).latest()
            except Subscription.DoesNotExist:
                return None
        return self._current_subscription

    def cancel(self):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        cu = stripe.Customer.retrieve(self.stripe_id)
        sub = cu.cancel_subscription()
        period_end = convert_tstamp(sub, "current_period_end")
        self.current_subscription.status = sub.status
        self.current_subscription.period_end = period_end
        self.current_subscription.save()

    @classmethod
    def create(cls, user):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        customer = stripe.Customer.create(
            email=user.email
        )
        return Customer.objects.create(
            user=user,
            stripe_id=customer.id
        )

    def update_card(self, token):
        stripe.api_key = settings.STRIPE_SECRET_KEY

        cu = stripe.Customer.retrieve(self.stripe_id)
        cu.card = token
        cu.save()
        print cu
        self.card_fingerprint = cu.active_card.fingerprint
        self.card_last_4 = cu.active_card.last4
        self.card_kind = cu.active_card.type
        self.save()

    def purchase(self, plan, trial_days=None):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        cu = stripe.Customer.retrieve(self.stripe_id)
        if settings.PAYMENTS_PLANS[plan].get("stripe_plan_id"):
            if trial_days:
                resp = cu.update_subscription(
                    plan=PAYMENTS_PLANS[plan]["stripe_plan_id"],
                    trial_end=timezone.now() + datetime.timedelta(days=trial_days)
                )
            else:
                resp = cu.update_subscription(
                    plan=PAYMENTS_PLANS[plan]["stripe_plan_id"]
                )
            sub_obj, created = self.subscriptions.get_or_create(
                plan=plan_from_stripe_id(resp["plan"]["id"]),
                period_start=convert_tstamp(resp, "current_period_start"),
                period_end=convert_tstamp(resp, "current_period_end"),
                defaults={
                    "amount": (resp["plan"]["amount"] / 100.0),
                    "status": resp["status"]
                }
            )
            if resp.get("trial_start") and resp.get("trial_end"):
                sub_obj.trial_period_start = convert_tstamp(resp, "trial_start")
                sub_obj.trial_period_end = convert_tstamp(resp, "trial_end")
                sub_obj.save()
            if not created:
                sub_obj.amount = (resp["plan"]["amount"] / 100.0)
                sub_obj.status = resp["status"]
                sub_obj.save()
        else:
            # It's just a single transaction
            resp = stripe.Charge.create(
                amount=PAYMENTS_PLANS[plan]["price"] * 100,
                currency="usd",
                customer=self.stripe_id,
                description=PAYMENTS_PLANS[plan]["name"]
            )
            obj = self.record_charge(resp)
            obj.description = resp["description"]
            obj.save()
            obj.send_receipt()
        self.plan = plan
        self.save()
        purchase_made.send(sender=self, plan=plan, stripe_response=resp)

    def record_charge(self, data):
        obj, created = self.charges.get_or_create(
            stripe_id=data["id"]
        )
        obj.card_last_4 = data["card"]["last4"]
        obj.card_kind = data["card"]["type"]
        obj.amount = (data["amount"] / 100.0)
        obj.paid = data["paid"]
        obj.refunded = data["refunded"]
        obj.fee = (data["fee"] / 100.0)
        obj.disputed = data["disputed"]
        if data.get("description"):
            obj.description = data["description"]
        if data.get("amount_refunded"):
            obj.amount_refunded = (data["amount_refunded"] / 100.0)
        if data["refunded"]:
            obj.amount_refunded = (data["amount"] / 100.0)
        obj.save()
        return obj

class Subscription(models.Model):

    plan = models.CharField(max_length=100)
    customer = models.ForeignKey(Customer, related_name="subscriptions")
    period_end = models.DateTimeField()
    period_start = models.DateTimeField()
    trial_period_end = models.DateTimeField(null=True)
    trial_period_start = models.DateTimeField(null=True)
    amount = models.DecimalField(decimal_places=2, max_digits=7)
    # trialing, active, past_due, canceled, or unpaid
    status = models.CharField(max_length=25)

    created_at = models.DateTimeField(default=timezone.now)

    def is_period_current(self):
        return self.period_end > timezone.now()

    def is_status_current(self):
        return self.status not in ["unpaid", "past_due"]

    def is_valid(self):
        return self.is_period_current() and self.is_status_current()

    class Meta:
        ordering = ["-period_end"]
        get_latest_by = "created_at"

class Invoice(StripeObject):

    customer = models.ForeignKey(Customer, related_name="invoices")
    attempted = models.NullBooleanField()
    attempts = models.PositiveIntegerField(null=True)
    closed = models.BooleanField()
    paid = models.BooleanField()
    period_end = models.DateTimeField()
    period_start = models.DateTimeField()
    subtotal = models.DecimalField(decimal_places=2, max_digits=7)
    total = models.DecimalField(decimal_places=2, max_digits=7)
    date = models.DateTimeField()
    charge = models.CharField(max_length=50, blank=True)
    subscriptions = models.ManyToManyField(
        Subscription,
        related_name="invoices",
        null=True
    )

    class Meta:
        ordering = ["-date"]

    def status(self):
        if self.paid:
            return "Paid"
        return "Open"

    @classmethod
    def create_from_stripe_data(cls, stripe_invoice):
        if not cls.objects.filter(stripe_id=stripe_invoice["id"]).exists():
            c = Customer.objects.get(stripe_id=stripe_invoice["customer"])

            period_end = convert_tstamp(stripe_invoice, "period_end")
            period_start = convert_tstamp(stripe_invoice, "period_start")
            date = convert_tstamp(stripe_invoice, "date")

            invoice = c.invoices.create(
                attempted=stripe_invoice["attempted"],
                closed=stripe_invoice["closed"],
                paid=stripe_invoice["paid"],
                period_end=period_end,
                period_start=period_start,
                subtotal=stripe_invoice["subtotal"] / 100.0,
                total=stripe_invoice["total"] / 100.0,
                date=date,
                charge=stripe_invoice["charge"] or "",
                stripe_id=stripe_invoice["id"]
            )
            for item in stripe_invoice["lines"].get("invoiceitems", []):
                invoice.items.get_or_create(
                    stripe_id=item["id"],
                    amount=(item["amount"] / 100.0),
                    date=convert_tstamp(item, "date"),
                    description=item["description"]
                )
            for sub in stripe_invoice["lines"].get("subscriptions", []):
                period_end = convert_tstamp(sub["period"], "end")
                period_start = convert_tstamp(sub["period"], "start")
                subscription, created = Subscription.objects.get_or_create(
                    plan=plan_from_stripe_id(sub["plan"]["id"]),
                    customer=c,
                    period_start=period_start,
                    period_end=period_end,
                    defaults={
                        "amount": (sub["amount"] / 100.0)
                    }
                )
                if not created:
                    subscription.amount = (sub["amount"] / 100.0)
                    subscription.save()
                # @@@ what happens if the same sub.pk gets added twice?
                invoice.subscriptions.add(subscription)

            if stripe_invoice["charge"]:
                charge = stripe.Charge.retrieve(stripe_invoice["charge"])
                desc = stripe_invoice["lines"]["subscriptions"][0]["plan"]["name"]
                obj = c.record_charge(charge)
                obj.invoice = invoice
                obj.description = desc
                obj.save()
                obj.send_receipt()
            return invoice

    @classmethod
    def handle_event(cls, event):
        valid_events = ["invoice.payment_failed", "invoice.payment_succeeded"]
        if event.kind in valid_events:
            invoice_data = event.message["data"]["object"]
            stripe_invoice = stripe.Invoice.retrieve(invoice_data["id"])
            cls.create_from_stripe_data(stripe_invoice)

class InvoiceItem(StripeObject):

    invoice = models.ForeignKey(Invoice, related_name="items")
    description = models.CharField(max_length=200, blank=True)
    date = models.DateTimeField()
    amount = models.DecimalField(decimal_places=2, max_digits=7)

class Charge(StripeObject):

    customer = models.ForeignKey(Customer, related_name="charges")
    invoice = models.ForeignKey(Invoice, null=True, related_name="charges")
    card_last_4 = models.CharField(max_length=4, blank=True)
    card_kind = models.CharField(max_length=50, blank=True)
    amount = models.DecimalField(decimal_places=2, max_digits=7, null=True)
    amount_refunded = models.DecimalField(
        decimal_places=2,
        max_digits=7,
        null=True
    )
    description = models.TextField(blank=True)
    paid = models.NullBooleanField(null=True)
    disputed = models.NullBooleanField(null=True)
    refunded = models.NullBooleanField(null=True)
    fee = models.DecimalField(decimal_places=2, max_digits=7, null=True)
    receipt_sent = models.BooleanField(default=False)

    def send_receipt(self):
        if not self.receipt_sent:
            site = Site.objects.get_current()
            subject = render_to_string(
                "payments/email/subject.txt",
                {"charge": self, "site": site}
            ).strip()
            message = render_to_string(
                "payments/email/body.txt",
                {"charge": self, "site": site}
            )
            num_sent = EmailMessage(
                subject,
                message,
                to=[self.customer.user.email],
                from_email=INVOICE_FROM_EMAIL
            ).send()
            self.receipt_sent = num_sent > 0
            self.save()
<commit_msg>Add some display methods<commit_after>import datetime
import json
import traceback

from django.conf import settings
from django.core.mail import EmailMessage
from django.db import models
from django.utils import timezone
from django.template.loader import render_to_string

from django.contrib.auth.models import User
from django.contrib.sites.models import Site

import stripe

from jsonfield.fields import JSONField

from payments.settings import PAYMENTS_PLANS, INVOICE_FROM_EMAIL
from payments.settings import plan_from_stripe_id
from payments.signals import WEBHOOK_SIGNALS
from payments.signals import purchase_made, webhook_processing_error

def convert_tstamp(response, field_name):
    try:
        if response[field_name]:
            return datetime.datetime.fromtimestamp(
                response[field_name],
                timezone.utc
            )
    except KeyError:
        pass
    return None

class StripeObject(models.Model):

    stripe_id = models.CharField(max_length=50, unique=True)
    created_at = models.DateTimeField(default=timezone.now)

    class Meta:
        abstract = True

class EventProcessingException(models.Model):

    event = models.ForeignKey("Event", null=True)
    data = models.TextField()
    message = models.CharField(max_length=500)
    traceback = models.TextField()
    created_at = models.DateTimeField(default=timezone.now)

    @classmethod
    def log(cls, data, exception, event):
        cls.objects.create(
            event=event,
            data=data or "",
            message=str(exception),
            traceback=traceback.format_exc()
        )

    def __unicode__(self):
        return u"<%s, pk=%s, Event=%s>" % (self.message, self.pk, self.event)

class Event(StripeObject):

    kind = models.CharField(max_length=250)
    livemode = models.BooleanField()
    customer = models.ForeignKey("Customer", null=True)
    webhook_message = JSONField()
    validated_message = JSONField(null=True)
    valid = models.NullBooleanField(null=True)
    processed = models.BooleanField(default=False)

    @property
    def message(self):
        return self.validated_message

    def __unicode__(self):
        return "%s - %s" % (self.kind, self.stripe_id)

    def link_customer(self):
        cus_id = None
        customer_crud_events = [
            "customer.created",
            "customer.updated",
            "customer.deleted"
        ]
        if self.kind in customer_crud_events:
            cus_id = self.message["data"]["object"]["id"]
        else:
            cus_id = self.message["data"]["object"]["customer"]

        if cus_id is not None:
            try:
                self.customer = Customer.objects.get(stripe_id=cus_id)
                self.save()
            except Customer.DoesNotExist:
                pass

    def validate(self):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        evt = stripe.Event.retrieve(self.stripe_id)
        self.validated_message = json.loads(
            json.dumps(
                evt.to_dict(),
                sort_keys=True,
                cls=stripe.StripeObjectEncoder
            )
        )
        if self.webhook_message["data"] == self.validated_message["data"]:
            self.valid = True
        else:
            self.valid = False
        self.save()

    def process(self):
        """
            "charge.succeeded",
            "charge.failed",
            "charge.refunded",
            "charge.updated",
            "charge.disputed",
            "customer.created",
            "customer.updated",
            "customer.deleted",
            "customer.subscription.created",
            "customer.subscription.updated",
            "customer.subscription.deleted",
            "customer.subscription.trial_will_end",
            "customer.discount.created",
            "customer.discount.updated",
            "customer.discount.deleted",
            "invoice.created",
            "invoice.updated",
            "invoice.payment_succeeded",
            "invoice.payment_failed",
            "invoiceitem.created",
            "invoiceitem.updated",
            "invoiceitem.deleted",
            "plan.created",
            "plan.updated",
            "plan.deleted",
            "coupon.created",
            "coupon.updated",
            "coupon.deleted",
            "transfer.created",
            "transfer.failed",
            "ping"
        """
        if self.valid and not self.processed:
            try:
                if not self.kind.startswith("plan."):
                    self.link_customer()
                if self.kind.startswith("invoice."):
                    Invoice.handle_event(self)
                elif self.kind.startswith("charge."):
                    self.customer.record_charge(self.message["data"]["object"])
                self.send_signal()
                self.processed = True
                self.save()
            except stripe.StripeError, e:
                EventProcessingException.log(
                    data=e.http_body,
                    exception=e,
                    event=self
                )
                webhook_processing_error.send(
                    sender=Event,
                    data=e.http_body,
                    exception=e
                )

    def send_signal(self):
        signal = WEBHOOK_SIGNALS.get(self.kind)
        if signal:
            return signal.send(sender=Event, event=self)

class Customer(StripeObject):

    user = models.OneToOneField(User)

    plan = models.CharField(max_length=100, blank=True)

    card_fingerprint = models.CharField(max_length=200, null=True)
    card_last_4 = models.CharField(max_length=4, null=True)
    card_kind = models.CharField(max_length=50, blank=True)

    def display_plan(self):
        if self.plan:
            return PAYMENTS_PLANS[self.plan]["name"]
        return ""

    def has_active_subscription(self):
        if not self.current_subscription:
            return False
        return self.current_subscription.is_valid()

    @property
    def current_subscription(self):
        if not hasattr(self, "_current_subscription"):
            try:
                self._current_subscription = self.subscriptions.exclude(
                    status__in=["past_due", "unpaid"]
                ).latest()
            except Subscription.DoesNotExist:
                return None
        return self._current_subscription

    def cancel(self):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        cu = stripe.Customer.retrieve(self.stripe_id)
        sub = cu.cancel_subscription()
        period_end = convert_tstamp(sub, "current_period_end")
        self.current_subscription.status = sub.status
        self.current_subscription.period_end = period_end
        self.current_subscription.save()

    @classmethod
    def create(cls, user):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        customer = stripe.Customer.create(
            email=user.email
        )
        return Customer.objects.create(
            user=user,
            stripe_id=customer.id
        )

    def update_card(self, token):
        stripe.api_key = settings.STRIPE_SECRET_KEY

        cu = stripe.Customer.retrieve(self.stripe_id)
        cu.card = token
        cu.save()
        print cu
        self.card_fingerprint = cu.active_card.fingerprint
        self.card_last_4 = cu.active_card.last4
        self.card_kind = cu.active_card.type
        self.save()

    def purchase(self, plan, trial_days=None):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        cu = stripe.Customer.retrieve(self.stripe_id)
        if settings.PAYMENTS_PLANS[plan].get("stripe_plan_id"):
            if trial_days:
                resp = cu.update_subscription(
                    plan=PAYMENTS_PLANS[plan]["stripe_plan_id"],
                    trial_end=timezone.now() + datetime.timedelta(days=trial_days)
                )
            else:
                resp = cu.update_subscription(
                    plan=PAYMENTS_PLANS[plan]["stripe_plan_id"]
                )
            sub_obj, created = self.subscriptions.get_or_create(
                plan=plan_from_stripe_id(resp["plan"]["id"]),
                period_start=convert_tstamp(resp, "current_period_start"),
                period_end=convert_tstamp(resp, "current_period_end"),
                defaults={
                    "amount": (resp["plan"]["amount"] / 100.0),
                    "status": resp["status"]
                }
            )
            if resp.get("trial_start") and resp.get("trial_end"):
                sub_obj.trial_period_start = convert_tstamp(resp, "trial_start")
                sub_obj.trial_period_end = convert_tstamp(resp, "trial_end")
                sub_obj.save()
            if not created:
                sub_obj.amount = (resp["plan"]["amount"] / 100.0)
                sub_obj.status = resp["status"]
                sub_obj.save()
        else:
            # It's just a single transaction
            resp = stripe.Charge.create(
                amount=PAYMENTS_PLANS[plan]["price"] * 100,
                currency="usd",
                customer=self.stripe_id,
                description=PAYMENTS_PLANS[plan]["name"]
            )
            obj = self.record_charge(resp)
            obj.description = resp["description"]
            obj.save()
            obj.send_receipt()
        self.plan = plan
        self.save()
        purchase_made.send(sender=self, plan=plan, stripe_response=resp)

    def record_charge(self, data):
        obj, created = self.charges.get_or_create(
            stripe_id=data["id"]
        )
        obj.card_last_4 = data["card"]["last4"]
        obj.card_kind = data["card"]["type"]
        obj.amount = (data["amount"] / 100.0)
        obj.paid = data["paid"]
        obj.refunded = data["refunded"]
        obj.fee = (data["fee"] / 100.0)
        obj.disputed = data["disputed"]
        if data.get("description"):
            obj.description = data["description"]
        if data.get("amount_refunded"):
            obj.amount_refunded = (data["amount_refunded"] / 100.0)
        if data["refunded"]:
            obj.amount_refunded = (data["amount"] / 100.0)
        obj.save()
        return obj

class Subscription(models.Model):

    plan = models.CharField(max_length=100)
    customer = models.ForeignKey(Customer, related_name="subscriptions")
    period_end = models.DateTimeField()
    period_start = models.DateTimeField()
    trial_period_end = models.DateTimeField(null=True)
    trial_period_start = models.DateTimeField(null=True)
    amount = models.DecimalField(decimal_places=2, max_digits=7)
    # trialing, active, past_due, canceled, or unpaid
    status = models.CharField(max_length=25)

    created_at = models.DateTimeField(default=timezone.now)

    def plan_display(self):
        return PAYMENTS_PLANS[self.plan]["name"]

    def status_display(self):
        return self.status.replace("_", " ").title()

    def is_period_current(self):
        return self.period_end > timezone.now()

    def is_status_current(self):
        return self.status not in ["unpaid", "past_due"]

    def is_valid(self):
        return self.is_period_current() and self.is_status_current()

    class Meta:
        ordering = ["-period_end"]
        get_latest_by = "created_at"

class Invoice(StripeObject):

    customer = models.ForeignKey(Customer, related_name="invoices")
    attempted = models.NullBooleanField()
    attempts = models.PositiveIntegerField(null=True)
    closed = models.BooleanField()
    paid = models.BooleanField()
    period_end = models.DateTimeField()
    period_start = models.DateTimeField()
    subtotal = models.DecimalField(decimal_places=2, max_digits=7)
    total = models.DecimalField(decimal_places=2, max_digits=7)
    date = models.DateTimeField()
    charge = models.CharField(max_length=50, blank=True)
    subscriptions = models.ManyToManyField(
        Subscription,
        related_name="invoices",
        null=True
    )

    class Meta:
        ordering = ["-date"]

    def status(self):
        if self.paid:
            return "Paid"
        return "Open"

    @classmethod
    def create_from_stripe_data(cls, stripe_invoice):
        if not cls.objects.filter(stripe_id=stripe_invoice["id"]).exists():
            c = Customer.objects.get(stripe_id=stripe_invoice["customer"])

            period_end = convert_tstamp(stripe_invoice, "period_end")
            period_start = convert_tstamp(stripe_invoice, "period_start")
            date = convert_tstamp(stripe_invoice, "date")

            invoice = c.invoices.create(
                attempted=stripe_invoice["attempted"],
                closed=stripe_invoice["closed"],
                paid=stripe_invoice["paid"],
                period_end=period_end,
                period_start=period_start,
                subtotal=stripe_invoice["subtotal"] / 100.0,
                total=stripe_invoice["total"] / 100.0,
                date=date,
                charge=stripe_invoice["charge"] or "",
                stripe_id=stripe_invoice["id"]
            )
            for item in stripe_invoice["lines"].get("invoiceitems", []):
                invoice.items.get_or_create(
                    stripe_id=item["id"],
                    amount=(item["amount"] / 100.0),
                    date=convert_tstamp(item, "date"),
                    description=item["description"]
                )
            for sub in stripe_invoice["lines"].get("subscriptions", []):
                period_end = convert_tstamp(sub["period"], "end")
                period_start = convert_tstamp(sub["period"], "start")
                subscription, created = Subscription.objects.get_or_create(
                    plan=plan_from_stripe_id(sub["plan"]["id"]),
                    customer=c,
                    period_start=period_start,
                    period_end=period_end,
                    defaults={
                        "amount": (sub["amount"] / 100.0)
                    }
                )
                if not created:
                    subscription.amount = (sub["amount"] / 100.0)
                    subscription.save()
                # @@@ what happens if the same sub.pk gets added twice?
                invoice.subscriptions.add(subscription)

            if stripe_invoice["charge"]:
                charge = stripe.Charge.retrieve(stripe_invoice["charge"])
                desc = stripe_invoice["lines"]["subscriptions"][0]["plan"]["name"]
                obj = c.record_charge(charge)
                obj.invoice = invoice
                obj.description = desc
                obj.save()
                obj.send_receipt()
            return invoice

    @classmethod
    def handle_event(cls, event):
        valid_events = ["invoice.payment_failed", "invoice.payment_succeeded"]
        if event.kind in valid_events:
            invoice_data = event.message["data"]["object"]
            stripe_invoice = stripe.Invoice.retrieve(invoice_data["id"])
            cls.create_from_stripe_data(stripe_invoice)

class InvoiceItem(StripeObject):

    invoice = models.ForeignKey(Invoice, related_name="items")
    description = models.CharField(max_length=200, blank=True)
    date = models.DateTimeField()
    amount = models.DecimalField(decimal_places=2, max_digits=7)

class Charge(StripeObject):

    customer = models.ForeignKey(Customer, related_name="charges")
    invoice = models.ForeignKey(Invoice, null=True, related_name="charges")
    card_last_4 = models.CharField(max_length=4, blank=True)
    card_kind = models.CharField(max_length=50, blank=True)
    amount = models.DecimalField(decimal_places=2, max_digits=7, null=True)
    amount_refunded = models.DecimalField(
        decimal_places=2,
        max_digits=7,
        null=True
    )
    description = models.TextField(blank=True)
    paid = models.NullBooleanField(null=True)
    disputed = models.NullBooleanField(null=True)
    refunded = models.NullBooleanField(null=True)
    fee = models.DecimalField(decimal_places=2, max_digits=7, null=True)
    receipt_sent = models.BooleanField(default=False)

    def send_receipt(self):
        if not self.receipt_sent:
            site = Site.objects.get_current()
            subject = render_to_string(
                "payments/email/subject.txt",
                {"charge": self, "site": site}
            ).strip()
            message = render_to_string(
                "payments/email/body.txt",
                {"charge": self, "site": site}
            )
            num_sent = EmailMessage(
                subject,
                message,
                to=[self.customer.user.email],
                from_email=INVOICE_FROM_EMAIL
            ).send()
            self.receipt_sent = num_sent > 0
            self.save()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

# Copyright(C) 2010  Romain Bignon
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

from __future__ import with_statement

from weboob.tools.backend import BaseBackend
from weboob.tools.newsfeed import Newsfeed
from weboob.tools.value import Value, ValueBool, ValuesDict
from weboob.capabilities.messages import ICapMessages, ICapMessagesPost, Message, Thread, CantSendMessage

from .browser import DLFP
from .tools import url2id

__all__ = ['DLFPBackend']

class DLFPBackend(BaseBackend, ICapMessages, ICapMessagesPost):
    NAME = 'dlfp'
    MAINTAINER = 'Romain Bignon'
    EMAIL = 'romain@weboob.org'
    VERSION = '0.4'
    LICENSE = 'GPLv3'
    DESCRIPTION = "Da Linux French Page"
    CONFIG = ValuesDict(Value('username',          label='Username', regexp='.+'),
                        Value('password',          label='Password', regexp='.+', masked=True),
                        ValueBool('get_news',      label='Get newspapers', default=True),
                        ValueBool('get_telegrams', label='Get telegrams', default=False))
    STORAGE = {'seen': {}}
    BROWSER = DLFP
    RSS_TELEGRAMS= "https://linuxfr.org/backend/journaux/rss20.rss"
    RSS_NEWSPAPERS = "https://linuxfr.org/backend/news/rss20.rss"

    def create_default_browser(self):
        return self.create_browser(self.config['username'], self.config['password'])

    def iter_threads(self):
        whats = set()
        if self.config['get_news']:
            whats.add(self.RSS_NEWSPAPERS)
        if self.config['get_telegrams']:
            whats.add(self.RSS_TELEGRAMS)

        for what in whats:
            for article in Newsfeed(what, url2id).iter_entries():
                thread = Thread(article.id)
                thread.title = article.title
                yield thread

    def get_thread(self, id):
        if isinstance(id, Thread):
            thread = id
            id = thread.id
        else:
            thread = None

        with self.browser:
            content = self.browser.get_content(id)

        if not thread:
            thread = Thread(id)

        flags = Message.IS_HTML
        if not thread.id in self.storage.get('seen', default={}):
            flags |= Message.IS_UNREAD

        thread.title = content.title
        if not thread.date:
            thread.date = content.date

        thread.root = Message(thread=thread,
                              id=0, # root message
                              title=content.title,
                              sender=content.author,
                              receiver=None,
                              date=thread.date, #TODO XXX WTF this is None
                              parent=None,
                              content=''.join([content.body, content.part2]),
                              signature='URL: %s' % content.url,
                              children=[],
                              flags=flags)

        for com in content.comments:
            self._insert_comment(com, thread.root)

        return thread

    def _insert_comment(self, com, parent):
        """"
        Insert 'com' comment and its children in the parent message.
        """
        flags = Message.IS_HTML
        if not com.id in self.storage.get('seen', parent.thread.id, 'comments', default=[]):
            flags |= Message.IS_UNREAD

        message = Message(thread=parent.thread,
                          id=com.id,
                          title=com.title,
                          sender=com.author,
                          receiver=None,
                          date=com.date,
                          parent=parent,
                          content=com.body,
                          signature='
'.join(['Score: %d' % com.score,
                                                   'URL: %s' % com.url]),
                          children=[],
                          flags=flags)

        parent.children.append(message)
        for sub in com.comments:
            self._insert_comment(sub, message)

    def iter_unread_messages(self, thread=None):
        for thread in self.iter_threads():
            self.fill_thread(thread, 'root')
            for m in thread.iter_all_messages():
                if m.flags & m.IS_UNREAD:
                    yield m

    def set_message_read(self, message):
        self.storage.set('seen', message.thread.id, 'comments', self.storage.get('seen', message.thread.id, 'comments', default=[]) + [message.id])
        self.storage.save()

    def post_message(self, message):
        if not message.parent:
            raise CantSendMessage('Posting news and telegrams on DLFP is not supported yet')

        assert message.thread

        with self.browser:
            return self.browser.post_reply(message.thread.id,
                                           message.parent.id,
                                           message.title,
                                           message.content,
                                           message.flags & message.IS_HTML)

    def fill_thread(self, thread, fields):
        return self.get_thread(thread)

    OBJECTS = {Thread: fill_thread}
<commit_msg>store datetime from newsfeed in the Thread object (closes #419)<commit_after># -*- coding: utf-8 -*-

# Copyright(C) 2010  Romain Bignon
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

from __future__ import with_statement

from weboob.tools.backend import BaseBackend
from weboob.tools.newsfeed import Newsfeed
from weboob.tools.value import Value, ValueBool, ValuesDict
from weboob.capabilities.messages import ICapMessages, ICapMessagesPost, Message, Thread, CantSendMessage

from .browser import DLFP
from .tools import url2id

__all__ = ['DLFPBackend']

class DLFPBackend(BaseBackend, ICapMessages, ICapMessagesPost):
    NAME = 'dlfp'
    MAINTAINER = 'Romain Bignon'
    EMAIL = 'romain@weboob.org'
    VERSION = '0.4'
    LICENSE = 'GPLv3'
    DESCRIPTION = "Da Linux French Page"
    CONFIG = ValuesDict(Value('username',          label='Username', regexp='.+'),
                        Value('password',          label='Password', regexp='.+', masked=True),
                        ValueBool('get_news',      label='Get newspapers', default=True),
                        ValueBool('get_telegrams', label='Get telegrams', default=False))
    STORAGE = {'seen': {}}
    BROWSER = DLFP
    RSS_TELEGRAMS= "https://linuxfr.org/backend/journaux/rss20.rss"
    RSS_NEWSPAPERS = "https://linuxfr.org/backend/news/rss20.rss"

    def create_default_browser(self):
        return self.create_browser(self.config['username'], self.config['password'])

    def iter_threads(self):
        whats = set()
        if self.config['get_news']:
            whats.add(self.RSS_NEWSPAPERS)
        if self.config['get_telegrams']:
            whats.add(self.RSS_TELEGRAMS)

        for what in whats:
            for article in Newsfeed(what, url2id).iter_entries():
                thread = Thread(article.id)
                thread.title = article.title
                if article.datetime:
                    thread.date = article.datetime
                yield thread

    def get_thread(self, id):
        if isinstance(id, Thread):
            thread = id
            id = thread.id
        else:
            thread = None

        with self.browser:
            content = self.browser.get_content(id)

        if not thread:
            thread = Thread(id)

        flags = Message.IS_HTML
        if not thread.id in self.storage.get('seen', default={}):
            flags |= Message.IS_UNREAD

        thread.title = content.title
        if not thread.date:
            thread.date = content.date

        thread.root = Message(thread=thread,
                              id=0, # root message
                              title=content.title,
                              sender=content.author,
                              receiver=None,
                              date=thread.date, #TODO XXX WTF this is None
                              parent=None,
                              content=''.join([content.body, content.part2]),
                              signature='URL: %s' % content.url,
                              children=[],
                              flags=flags)

        for com in content.comments:
            self._insert_comment(com, thread.root)

        return thread

    def _insert_comment(self, com, parent):
        """"
        Insert 'com' comment and its children in the parent message.
        """
        flags = Message.IS_HTML
        if not com.id in self.storage.get('seen', parent.thread.id, 'comments', default=[]):
            flags |= Message.IS_UNREAD

        message = Message(thread=parent.thread,
                          id=com.id,
                          title=com.title,
                          sender=com.author,
                          receiver=None,
                          date=com.date,
                          parent=parent,
                          content=com.body,
                          signature='
'.join(['Score: %d' % com.score,
                                                   'URL: %s' % com.url]),
                          children=[],
                          flags=flags)

        parent.children.append(message)
        for sub in com.comments:
            self._insert_comment(sub, message)

    def iter_unread_messages(self, thread=None):
        for thread in self.iter_threads():
            self.fill_thread(thread, 'root')
            for m in thread.iter_all_messages():
                if m.flags & m.IS_UNREAD:
                    yield m

    def set_message_read(self, message):
        self.storage.set('seen', message.thread.id, 'comments', self.storage.get('seen', message.thread.id, 'comments', default=[]) + [message.id])
        self.storage.save()

    def post_message(self, message):
        if not message.parent:
            raise CantSendMessage('Posting news and telegrams on DLFP is not supported yet')

        assert message.thread

        with self.browser:
            return self.browser.post_reply(message.thread.id,
                                           message.parent.id,
                                           message.title,
                                           message.content,
                                           message.flags & message.IS_HTML)

    def fill_thread(self, thread, fields):
        return self.get_thread(thread)

    OBJECTS = {Thread: fill_thread}
<|endoftext|>


<commit_before># Copyright 2014 Dave Kludt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from flask import Flask, render_template, g, request
from happymongo import HapPyMongo
from datetime import timedelta
from config import config

import re
import json

# Start import for all blueprints
from adminbp import bp as admin_bp
from cloud_backup import bp as backup_bp
from block_storage import bp as cbs_bp
from databases import bp as db_bp
from dns import bp as dns_bp
from first_gen_servers import bp as fg_bp
from load_balancers import bp as lb_bp
from networks import bp as networks_bp
from identity import bp as identity_bp
from next_gen_servers import bp as ng_bp
from monitoring import bp as mon_bp
from queues import bp as queue_bp
from autoscale import bp as autoscale_bp
from images import bp as images_bp
from big_data import bp as bigdata_bp
from orchestration import bp as orchestration_bp
from cloud_feeds import bp as cloudfeeds_bp
from manage_globals import bp as manage_bp
from reporting import bp as report_bp
# End Blueprint import

app = Flask(__name__)
app.config.from_object(config)
app.register_blueprint(admin_bp, url_prefix='/admin')
app.register_blueprint(backup_bp, url_prefix='/cloud_backup')
app.register_blueprint(cbs_bp, url_prefix='/block_storage')
app.register_blueprint(db_bp, url_prefix='/databases')
app.register_blueprint(dns_bp, url_prefix='/dns')
app.register_blueprint(fg_bp, url_prefix='/fg_servers')
app.register_blueprint(lb_bp, url_prefix='/load_balancers')
app.register_blueprint(networks_bp, url_prefix='/networks')
app.register_blueprint(identity_bp, url_prefix='/identity')
app.register_blueprint(ng_bp, url_prefix='/ng_servers')
app.register_blueprint(mon_bp, url_prefix='/monitoring')
app.register_blueprint(queue_bp, url_prefix='/queues')
app.register_blueprint(autoscale_bp, url_prefix='/autoscale')
app.register_blueprint(images_bp, url_prefix='/images')
app.register_blueprint(bigdata_bp, url_prefix='/big_data')
app.register_blueprint(orchestration_bp, url_prefix='/orchestration')
app.register_blueprint(cloudfeeds_bp, url_prefix='/cloud_feeds')
app.register_blueprint(manage_bp, url_prefix='/manage')
app.register_blueprint(report_bp, url_prefix='/reporting')

# Setup DB based on the app name
mongo, db = HapPyMongo(config)

from global_api_functions import front_page_most_accessed, \
    search_for_calls, gather_history

@app.template_filter()
def nl2br(value):
    _newline_re = re.compile(r'(?:\r\n|\r|\n)')
    return _newline_re.sub('
', value)

@app.template_filter()
def tab2spaces(value):
    text = re.sub('\t', ' ' * 4, value)
    return text

@app.template_filter()
def unslug(value):
    text = re.sub('_', ' ', value)
    return text

@app.template_filter()
def slug(value):
    text = re.sub('\s+', '_', value)
    return text

@app.template_filter()
def check_regex(value):
    if re.match('variable', value):
        return True
    else:
        return False

@app.context_processor
def utility_processor():
    def parse_field_data(value):
        choices = re.sub('\r\n', ',', value)
        return choices.split(',')

    def to_json(value):
        temp = json.dumps(
            value,
            sort_keys=False,
            indent=4,
            separators=(',', ':')
        )
        return temp

    return dict(
        parse_field_data=parse_field_data,
        to_json=to_json
    )

# Set g to the db so that each blueprint can use it
@app.before_request
def before_request():
    g.mongo = mongo
    g.db = db

@app.route('/')
def index():
    active_products, data_centers, = [], []
    api_settings = db.api_settings.find_one()
    if api_settings:
        active_products = api_settings.get('active_products')

    most_accessed = front_page_most_accessed(active_products)

    if api_settings:
        data_centers = api_settings.get('dcs')

    return render_template(
        'index.html',
        api_settings=api_settings,
        active_products=active_products,
        most_accessed=most_accessed,
        data_centers=data_centers
    )

@app.route('/search', methods=['POST'])
def search():
    search_string = request.json.get('search_string')
    api_results = search_for_calls(search_string)
    return render_template(
        '_api_call_wrapper_template.html',
        most_accessed=api_results
    )

@app.route('/history')
def history():
    active_products = None
    api_settings = g.db.api_settings.find_one()

    if api_settings:
        active_products = api_settings.get('active_products')

    history = gather_history()
    return render_template(
        'history.html',
        history=history,
        api_settings=api_settings,
        active_products=active_products
    )

if __name__ == '__main__':
    app.run(debug=True)
<commit_msg>Remove import and register for all product blueprints<commit_after># Copyright 2014 Dave Kludt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from flask import Flask, render_template, g, request
from happymongo import HapPyMongo
from datetime import timedelta
from config import config

import re
import json

# Start import for all blueprints
from adminbp import bp as admin_bp
from manage_globals import bp as manage_bp
from reporting import bp as report_bp
# End Blueprint import

app = Flask(__name__)
app.config.from_object(config)
app.register_blueprint(admin_bp, url_prefix='/admin')
app.register_blueprint(manage_bp, url_prefix='/manage')
app.register_blueprint(report_bp, url_prefix='/reporting')

# Setup DB based on the app name
mongo, db = HapPyMongo(config)

from global_api_functions import front_page_most_accessed, \
    search_for_calls, gather_history

@app.template_filter()
def nl2br(value):
    _newline_re = re.compile(r'(?:\r\n|\r|\n)')
    return _newline_re.sub('
', value)

@app.template_filter()
def tab2spaces(value):
    text = re.sub('\t', ' ' * 4, value)
    return text

@app.template_filter()
def unslug(value):
    text = re.sub('_', ' ', value)
    return text

@app.template_filter()
def slug(value):
    text = re.sub('\s+', '_', value)
    return text

@app.template_filter()
def check_regex(value):
    if re.match('variable', value):
        return True
    else:
        return False

@app.context_processor
def utility_processor():
    def parse_field_data(value):
        choices = re.sub('\r\n', ',', value)
        return choices.split(',')

    def to_json(value):
        temp = json.dumps(
            value,
            sort_keys=False,
            indent=4,
            separators=(',', ':')
        )
        return temp

    return dict(
        parse_field_data=parse_field_data,
        to_json=to_json
    )

# Set g to the db so that each blueprint can use it
@app.before_request
def before_request():
    g.mongo = mongo
    g.db = db

@app.route('/')
def index():
    active_products, data_centers, = [], []
    api_settings = db.api_settings.find_one()
    if api_settings:
        active_products = api_settings.get('active_products')

    most_accessed = front_page_most_accessed(active_products)

    if api_settings:
        data_centers = api_settings.get('dcs')

    return render_template(
        'index.html',
        api_settings=api_settings,
        active_products=active_products,
        most_accessed=most_accessed,
        data_centers=data_centers
    )

@app.route('/search', methods=['POST'])
def search():
    search_string = request.json.get('search_string')
    api_results = search_for_calls(search_string)
    return render_template(
        '_api_call_wrapper_template.html',
        most_accessed=api_results
    )

@app.route('/history')
def history():
    active_products = None
    api_settings = g.db.api_settings.find_one()

    if api_settings:
        active_products = api_settings.get('active_products')

    history = gather_history()
    return render_template(
        'history.html',
        history=history,
        api_settings=api_settings,
        active_products=active_products
    )

if __name__ == '__main__':
    app.run(debug=True)
<|endoftext|>