Exemplo n.º 1
0
def banktransfer(request):
    if any(k not in request.GET for k in ('invoice', 'key', 'prv')):
        return HttpResponse("Required parameter missing")
    invoice = get_object_or_404(Invoice, pk=get_int_or_error(request.GET, 'invoice'), recipient_secret=request.GET['key'])
    method = get_object_or_404(InvoicePaymentMethod, pk=get_int_or_error(request.GET, 'prv'))
    wrapper = PaymentMethodWrapper(method, invoice)

    return HttpResponse(wrapper.implementation.render_page(request, invoice))
Exemplo n.º 2
0
def wikipage_history(request, confurl, wikiurl):
    conference = get_object_or_404(Conference, urlname=confurl)
    page = get_object_or_404(Wikipage, conference=conference, url=wikiurl)
    reg = _check_wiki_permissions(request, page)
    if not page.history:
        raise PermissionDenied()

    fromid = toid = None

    if request.method == 'POST':
        # View a diff
        if not ('from' in request.POST and 'to' in request.POST):
            messages.warning(request,
                             "Must specify both source and target version")
            return HttpResponseRedirect('.')

        page_from = get_object_or_404(WikipageHistory,
                                      page=page,
                                      pk=get_int_or_error(
                                          request.POST, 'from'))
        fromid = page_from.id
        if request.POST['to'] != '-1':
            page_to = get_object_or_404(WikipageHistory,
                                        page=page,
                                        pk=get_int_or_error(
                                            request.POST, 'to'))
            toid = page_to.id
        else:
            page_to = page
            toid = None

        diff = "\n".join(
            difflib.unified_diff(
                page_from.contents.split('\r\n'),
                page_to.contents.split('\r\n'),
                fromfile='{0}'.format(page_from.publishedat),
                tofile='{0}'.format(page_to.publishedat),
                lineterm='',
            ))
    else:
        diff = ''

    return render_conference_response(request, conference, 'wiki',
                                      'confwiki/wikipage_history.html', {
                                          'page': page,
                                          'diff': diff,
                                          'fromid': fromid,
                                          'toid': toid,
                                      })
Exemplo n.º 3
0
def multiregs(request, urlname):
    conference = get_authenticated_conference(request, urlname)

    return render(request, 'confreg/admin_multireg_list.html', {
        'conference': conference,
        'bulkpays': BulkPayment.objects.select_related('user', 'invoice__paidusing').prefetch_related('conferenceregistration_set').filter(conference=conference).order_by('-paidat', '-createdat'),
        'highlight': get_int_or_error(request.GET, 'b', -1),
        'helplink': 'registrations',
    })
Exemplo n.º 4
0
 def get_query(self, request, term):
     q = super(RegistrationLookup, self).get_query(request, term)
     if 'conference' in request.GET:
         return q.filter(conference_id=get_int_or_error(
             request.GET, 'conference'),
                         payconfirmedat__isnull=False)
     else:
         # Don't return anything if parameter not present
         return None
Exemplo n.º 5
0
def api(request, urlname, regtoken, what):
    (conference, user, is_admin) = _get_checkin(request, urlname, regtoken)

    if what == 'status':
        return _json_response({
            'user': user.attendee.username,
            'name': user.fullname,
            'active': conference.checkinactive,
            'confname': conference.conferencename,
            'admin': is_admin,
        })

    # Only the stats API call is allowed when check-in is not open
    if not conference.checkinactive and what != 'stats':
        return HttpResponse("Check-in not open", status=412)

    if what == 'lookup':
        token = request.GET.get('lookup')
        if not (token.startswith('ID$') and token.endswith('$ID')):
            raise Http404()
        token = token[3:-3]
        r = get_object_or_404(ConferenceRegistration,
                              conference=conference,
                              payconfirmedat__isnull=False,
                              idtoken=token)
        return _json_response({'reg': _get_reg_json(r)})
    elif what == 'search':
        s = request.GET.get('search').strip()
        return _json_response({
            'regs': [
                _get_reg_json(r)
                for r in ConferenceRegistration.objects.filter(
                    Q(firstname__icontains=s) | Q(lastname__icontains=s),
                    conference=conference,
                    payconfirmedat__isnull=False)
            ],
        })
    elif is_admin and what == 'stats':
        return _json_response(_get_statistics(conference))
    elif request.method == 'POST' and what == 'checkin':
        if not conference.checkinactive:
            return HttpResponse("Check-in not open", status=412)

        reg = get_object_or_404(ConferenceRegistration,
                                conference=conference,
                                payconfirmedat__isnull=False,
                                pk=get_int_or_error(request.POST, 'reg'))
        if reg.checkedinat:
            return HttpResponse("Already checked in.", status=412)
        reg.checkedinat = datetime.datetime.now()
        reg.checkedinby = user
        reg.save()
        return _json_response({
            'reg': _get_reg_json(reg),
        })
    else:
        raise Http404()
Exemplo n.º 6
0
def bankfile_transactions(request, methodid):
    authenticate_backend_group(request, 'Invoice managers')

    method = get_object_or_404(InvoicePaymentMethod, pk=methodid)

    # Needed for backlinks
    methodcount = InvoicePaymentMethod.objects.filter(
        config__has_key='file_upload_interval').count()

    backbutton = "../"
    breadlabel = "Bank transactions"

    if methodcount == 1:
        # If there is only one method, we have to return all the way back to the index page, or we'll
        # just get redirected back to ourselves.
        backbutton = "/admin/"

    q = Q(method=method)
    if 'file' in request.GET:
        q = q & Q(fromfile=get_int_or_error(request.GET, 'file'))
        backbutton = "../../"
        breadlabel = "Bankfiles"

    allrows = BankStatementRow.objects.filter(q).order_by('-date', 'id')
    (rows, paginator, page_range) = simple_pagination(request, allrows, 50)

    extrakeys = set()
    hasvaluefor = {
        'uniqueid': False,
        'balance': False,
    }
    for r in rows:
        extrakeys.update(r.other.keys())
        for k in hasvaluefor.keys():
            if getattr(r, k, None):
                hasvaluefor[k] = True

    params = request.GET.copy()
    if 'page' in params:
        del params['page']

    return render(
        request, 'invoices/bankfile_transactions.html', {
            'rows': rows,
            'extrakeys': extrakeys,
            'hasvaluefor': hasvaluefor,
            'page_range': page_range,
            'topadmin': 'Invoices',
            'helplink': 'payment',
            'requestparams': params.urlencode(),
            'breadcrumbs': [
                (backbutton, breadlabel),
            ],
            'backbutton': backbutton,
        })
Exemplo n.º 7
0
def banktransactions_match_invoice(request, transid, invoiceid):
    authenticate_backend_group(request, 'Invoice managers')

    trans = get_object_or_404(PendingBankTransaction, pk=transid)
    invoice = get_object_or_404(Invoice, pk=invoiceid)

    pm = trans.method.get_implementation()

    if request.method == 'POST':
        if pm.config('feeaccount'):
            fee_account = Account.objects.get(num=pm.config('feeaccount'))
        else:
            fee_account = get_object_or_404(Account, num=get_int_or_error(request.POST, 'account'))

        r = _flag_invoices(request,
                           trans,
                           [invoice, ],
                           pm,
                           fee_account)

        if r:
            return HttpResponseRedirect("/admin/invoices/banktransactions/")
        else:
            return HttpResponseRedirect(".")

    # Generate the form

    if pm.config('feeaccount'):
        fee_account = Account.objects.get(num=pm.config('feeaccount'))
        accounts = []
    else:
        fee_account = None
        accounts = get_account_choices()

    return render(request, 'invoices/banktransactions_match_invoice.html', {
        'transaction': trans,
        'invoices': [invoice, ],
        'topadmin': 'Invoices',
        'fee_account': fee_account,
        'accounts': accounts,
        'match': {
            'amountdiff': invoice.total_amount - trans.amount,
            'absdiff': abs(invoice.total_amount - trans.amount),
            'percentdiff': (abs(invoice.total_amount - trans.amount) / invoice.total_amount) * 100,
            'found_ref': invoice.payment_reference in trans.transtext,
            'found_id': str(invoice.id) in trans.transtext,
            'highlight_ref': re.sub('({0})'.format(invoice.payment_reference), r'<strong>\1</strong>', escape(trans.transtext)),
            'highlight_id': re.sub('({0})'.format(invoice.id), r'<strong>\1</strong>', escape(trans.transtext)),
        },
        'breadcrumbs': [
            ('/admin/invoices/banktransactions/', 'Pending bank transactions'),
            ('/admin/invoices/banktransactions/{0}/'.format(trans.id), 'Transaction'),
        ],
        'helplink': 'payment',
    })
Exemplo n.º 8
0
def bankfiles(request):
    authenticate_backend_group(request, 'Invoice managers')

    if request.method == 'POST':
        # Uploading a file!
        method = get_object_or_404(InvoicePaymentMethod,
                                   active=True,
                                   config__has_key='file_upload_interval',
                                   id=get_int_or_error(request.POST, 'id'))
        if 'f' not in request.FILES:
            messages.error(request, "No file included in upload")
        elif request.FILES['f'].size < 1:
            messages.error(request, "Uploaded file is empty")
        else:
            f = request.FILES['f']
            impl = method.get_implementation()

            try:
                (contents, numrows, numtrans,
                 numpending) = impl.parse_uploaded_file(f)
                BankFileUpload(
                    method=method,
                    uploadby=request.user.username,
                    name=f.name,
                    textcontents=contents,
                    parsedrows=numrows,
                    newtrans=numtrans,
                    newpending=numpending,
                ).save()
                messages.info(
                    request,
                    "File uploaded. {} rows parsed, {} transactions stored, resulting in {} pending transactions."
                    .format(numrows, numtrans, numpending))
                return HttpResponseRedirect('.')
            except Exception as e:
                messages.error(request, "Error uploading file: {}".format(e))

    methods = InvoicePaymentMethod.objects.filter(
        active=True, config__has_key='file_upload_interval').annotate(
            latest_file=Max('bankfileupload__created'))
    file_objects = BankFileUpload.objects.select_related(
        'method').all().order_by('-created')[:1000]
    (files, paginator, page_range) = simple_pagination(request, file_objects,
                                                       50)

    return render(
        request, 'invoices/bankfiles.html', {
            'files': files,
            'page_range': page_range,
            'methods': methods,
            'topadmin': 'Invoices',
            'helplink': 'payment',
        })
Exemplo n.º 9
0
def feedback_sessions(request, confname):
    conference = get_authenticated_conference(request, confname)

    # Get all sessions that have actual comments on them
    cursor = connection.cursor()
    cursor.execute(
        "SELECT concat(s.title, ' (' || (SELECT string_agg(fullname, ', ') FROM confreg_speaker spk INNER JOIN confreg_conferencesession_speaker css ON css.speaker_id=spk.id WHERE css.conferencesession_id=s.id) || ')'), conference_feedback FROM confreg_conferencesessionfeedback fb INNER JOIN confreg_conferencesession s ON fb.session_id=s.id WHERE s.conference_id=%s AND NOT conference_feedback='' ORDER BY 1,2"
        % (conference.id, ))
    commented_sessions = cursor.fetchall()

    # Now for all of our fancy toplists
    # The django ORM just can't do this...
    minvotes = 10
    if request.method == 'POST':
        minvotes = get_int_or_error(request.POST, 'minvotes')

    toplists = []

    # Start with top sessions
    toplists.extend(
        build_toplists(
            'Sessions',
            "SELECT s.title || ' (' || (SELECT string_agg(fullname, ', ') FROM confreg_speaker spk INNER JOIN confreg_conferencesession_speaker css ON css.speaker_id=spk.id WHERE css.conferencesession_id=s.id) || ')', avg(fb.{{key}}), count(*), stddev(fb.{{key}}) FROM confreg_conferencesessionfeedback fb INNER JOIN confreg_conferencesession s ON fb.session_id=s.id WHERE s.conference_id=%s AND fb.{{key}}>0 GROUP BY s.id HAVING count(*)>=%s ORDER BY 2 DESC"
            % (conference.id, minvotes)))

    # Now let's do the speakers
    toplists.extend(
        build_toplists(
            'Speakers',
            "SELECT (SELECT string_agg(fullname, ', ') FROM confreg_speaker spk INNER JOIN confreg_conferencesession_speaker css ON css.speaker_id=spk.id WHERE css.conferencesession_id=s.id) AS speakername, avg(fb.{{key}}), count(*), stddev(fb.{{key}}) FROM confreg_conferencesessionfeedback fb INNER JOIN confreg_conferencesession s ON fb.session_id=s.id WHERE s.conference_id=%s AND fb.{{key}}>0 GROUP BY speakername HAVING count(*)>=%s ORDER BY 2 DESC"
            % (conference.id, minvotes)))

    return render(
        request, 'confreg/admin_session_feedback.html', {
            'conference':
            conference,
            'toplists':
            toplists,
            'minvotes':
            minvotes,
            'commented_sessions':
            commented_sessions,
            'breadcrumbs': (('/events/admin/{0}/reports/feedback/'.format(
                conference.urlname), 'Feedback'), ),
            'helplink':
            'feedback',
        })
Exemplo n.º 10
0
def year(request, year):
    authenticate_backend_group(request, 'Accounting managers')

    try:
        year = Year.objects.get(year=int(year))
    except Year.DoesNotExist:
        # Year does not exist, but what do we do about it?
        if int(year) == date.today().year:
            # For current year, we automatically create the year and move on
            year = Year(year=int(year), isopen=True)
            year.save()
            messages.info(
                request,
                "Year {} did not exist in the system, but since it's the current year it has now been created."
                .format(year.year))
        else:
            return HttpResponse(
                "Year {} does not exist in the system, and is not current year."
                .format(int(year)))

    if 'search' in request.GET:
        _setup_search(request, request.GET['search'])
        return HttpResponseRedirect('/accounting/%s/' % year.year)

    (searchterm, entries) = _perform_search(request, year)

    paginator = EntryPaginator(entries)
    currpage = get_int_or_error(request.GET, 'p', 1)

    return render(
        request, 'accounting/main.html', {
            'entries': paginator.page(currpage),
            'page': currpage,
            'pages': paginator.get_pages(currpage),
            'numpages': paginator.num_pages,
            'hasopen': any([not e.closed for e in entries]),
            'year': year,
            'years': Year.objects.all(),
            'reportable_objects': _get_reportable_objects(year),
            'searchterm': searchterm,
        })
Exemplo n.º 11
0
def index(request):
    if not request.user.is_superuser:
        raise PermissionDenied("Access denied")

    config = get_config()

    if request.method == 'POST':
        if request.POST.get('submit') == 'Hold all jobs':
            config.hold_all_jobs = True
            config.save()
            notify_job_change()
            return HttpResponseRedirect('.')
        elif request.POST.get('submit') == 'Re-enable job execution':
            what = get_int_or_error(request.POST, 'pending')
            if what == 0:
                messages.error(request,
                               "Must decide what to do with pending jobs")
                return HttpResponseRedirect(".")
            elif what not in (1, 2):
                messages.error(request, "Invalid choice for pending jobs")
                return HttpResponseRedirect(".")

            if what == 2:
                # Re-schedule all pending jobs
                for job in ScheduledJob.objects.filter(
                        enabled=True, nextrun__lte=timezone.now()):
                    reschedule_job(job, save=True)
            config.hold_all_jobs = False
            config.save()
            notify_job_change()
            messages.info(request, "Job execution re-enabled")
            return HttpResponseRedirect(".")

        raise Http404("Unknown button")

    jobs = ScheduledJob.objects.all().order_by(
        F('nextrun').asc(nulls_last=True))
    try:
        lastjob = ScheduledJob.objects.only('lastrun').filter(
            lastrun__isnull=False).order_by('-lastrun')[0]
        lastjobtime = lastjob.lastrun
        lastjob_recent = (timezone.now() - lastjobtime) < timedelta(hours=6)
    except IndexError:
        # No job has run yet, can happen on brand new installation
        lastjobtime = None
        lastjob_recent = False

    history = JobHistory.objects.only(
        'time', 'job__description', 'success',
        'runtime').select_related('job').order_by('-time')[:20]

    with connection.cursor() as curs:
        curs.execute(
            "SELECT count(1) FROM pg_stat_activity WHERE application_name='pgeu scheduled job runner' AND datname=current_database()"
        )
        n, = curs.fetchone()
        runner_active = (n > 0)

    return render(
        request, 'scheduler/index.html', {
            'jobs': jobs,
            'lastjob': lastjobtime,
            'lastjob_recent': lastjob_recent,
            'runner_active': runner_active,
            'holdall': config.hold_all_jobs,
            'history': history,
            'apps': {a.name: a.verbose_name
                     for a in apps.get_app_configs()},
            'helplink': 'jobs',
        })
Exemplo n.º 12
0
def volunteerschedule_api(request, urlname, adm=False):
    try:
        (conference, can_admin,
         reg) = _get_conference_and_reg(request, urlname)
    except ConferenceRegistration.DoesNotExist:
        raise PermissionDenied()

    is_admin = can_admin and adm

    if request.method == 'GET':
        # GET just always returns the complete volunteer schedule
        slots = VolunteerSlot.objects.prefetch_related(
            'volunteerassignment_set',
            'volunteerassignment_set__reg').filter(conference=conference)
        return HttpResponse(json.dumps({
            'slots': [_slot_return_data(slot) for slot in slots],
            'volunteers': [{
                'id': vol.id,
                'name': vol.fullname,
            } for vol in conference.volunteers.all().order_by(
                'firstname', 'lastname')],
            'meta': {
                'isadmin': is_admin,
                'regid': reg.id,
            },
            'stats':
            _get_volunteer_stats(conference),
        }),
                            content_type='application/json')

    if request.method != 'POST':
        raise Http404()

    if 'op' not in request.POST:
        raise Http404()

    slotid = get_int_or_error(request.POST, 'slotid')
    volid = get_int_or_error(request.POST, 'volid')

    # We should always have a valid slot
    slot = get_object_or_404(VolunteerSlot, conference=conference, pk=slotid)

    err = None

    if request.POST['op'] == 'signup':
        if volid != 0:
            raise PermissionDenied("Invalid post data")
        err = _signup(request, conference, reg, is_admin, slot)
    elif request.POST['op'] == 'remove':
        err = _remove(request, conference, reg, is_admin, slot, volid)
    elif request.POST['op'] == 'confirm':
        err = _confirm(request, conference, reg, is_admin, slot, volid)
    elif request.POST['op'] == 'add':
        err = _add(request, conference, reg, is_admin, slot, volid)
    else:
        raise Http404()

    if err:
        return HttpResponse(
            json.dumps({'err': err}),
            content_type='application/json',
            status=500,
        )

    # Req-query the database to pick up any changes, and return the complete object
    slot = VolunteerSlot.objects.prefetch_related(
        'volunteerassignment_set',
        'volunteerassignment_set__reg').filter(conference=conference,
                                               pk=slot.pk)[0]
    return HttpResponse(json.dumps({
        'err': None,
        'slot': _slot_return_data(slot),
        'stats': _get_volunteer_stats(conference),
    }),
                        content_type='application/json')
Exemplo n.º 13
0
def volunteer_twitter(request, urlname, token):
    try:
        conference = Conference.objects.select_related('series').get(
            urlname=urlname)
    except Conference.DoesNotExist:
        raise Http404()

    if not conference.has_social_broadcast:
        raise Http404()

    reg = get_object_or_404(ConferenceRegistration,
                            conference=conference,
                            regtoken=token)
    if conference.administrators.filter(pk=reg.attendee_id).exists(
    ) or conference.series.administrators.filter(pk=reg.attendee_id):
        is_admin = True
        canpost = conference.twitter_postpolicy != 0
        canpostdirect = conference.twitter_postpolicy != 0
        canmoderate = conference.twitter_postpolicy in (2, 3)
    elif not conference.volunteers.filter(pk=reg.pk).exists():
        raise Http404()
    else:
        is_admin = False
        canpost = conference.twitter_postpolicy >= 2
        canpostdirect = conference.twitter_postpolicy == 4
        canmoderate = conference.twitter_postpolicy == 3

    providers = ProviderCache()

    if request.method == 'POST':
        if request.POST.get('op', '') == 'post':
            approved = False
            approvedby = None
            if is_admin:
                if conference.twitter_postpolicy == 0:
                    raise PermissionDenied()

                # Admins can use the bypass parameter to, well, bypass
                if request.POST.get('bypass', '0') == '1':
                    approved = True
                    approvedby = reg.attendee
            else:
                if conference.twitter_postpolicy in (0, 1):
                    raise PermissionDenied()

                if conference.twitter_postpolicy == 4:
                    # Post without approval for volunteers
                    approved = True
                    approvedby = reg.attendee

            # Check if we have *exactly the same tweet* in the queue already, in the past 5 minutes.
            # in which case it's most likely a clicked-too-many-times.
            if ConferenceTweetQueue.objects.filter(
                    conference=conference,
                    contents=request.POST['txt'],
                    author=reg.attendee,
                    datetime__gt=timezone.now() -
                    datetime.timedelta(minutes=5)):
                return _json_response({'error': 'Duplicate post detected'})

            # Now insert it in the queue, bypassing time validation since it's not an automatically
            # generated tweet.
            t = ConferenceTweetQueue(
                conference=conference,
                contents=request.POST['txt'][:280],
                approved=approved,
                approvedby=approvedby,
                author=reg.attendee,
                replytotweetid=request.POST.get('replyid', None),
            )
            if 'image' in request.FILES:
                t.image = request.FILES['image'].read()
                # Actually validate that it loads as PNG or JPG
                try:
                    p = ImageFile.Parser()
                    p.feed(t.image)
                    p.close()
                    image = p.image
                    if image.format not in ('PNG', 'JPEG'):
                        return _json_response({
                            'error':
                            'Image must be PNG or JPEG, not {}'.format(
                                image.format)
                        })
                except Exception as e:
                    return _json_response({'error': 'Failed to parse image'})

                MAXIMAGESIZE = 1 * 1024 * 1024
                if len(t.image) > MAXIMAGESIZE:
                    # Image is bigger than 4Mb, but it is a valid image, so try to rescale it
                    # We can't know exactly how to resize it to get it to the right size, but most
                    # likely if we cut the resolution by n% the filesize goes down by > n% (usually
                    # an order of magnitude), so we base it on that and just fail again if that didn't
                    # work.
                    rescalefactor = MAXIMAGESIZE / len(t.image)
                    newimg = image.resize((int(image.size[0] * rescalefactor),
                                           int(image.size[1] * rescalefactor)),
                                          Image.ANTIALIAS)
                    b = io.BytesIO()
                    newimg.save(b, image.format)
                    t.image = b.getvalue()
                    if len(t.image) > MAXIMAGESIZE:
                        return _json_response({
                            'error':
                            'Image file too big and automatic resize failed'
                        })

            t.save()
            if request.POST.get('replyid', None):
                orig = ConferenceIncomingTweet.objects.select_related(
                    'provider').get(conference=conference,
                                    statusid=get_int_or_error(
                                        request.POST, 'replyid'))
                orig.processedat = timezone.now()
                orig.processedby = reg.attendee
                orig.save()
                # When when replying to a tweet, it goes to the original provider *only*
                t.remainingtosend.set([orig.provider])

            return _json_response({})
        elif request.POST.get('op', None) in ('approve', 'discard'):
            if not is_admin:
                # Admins can always approve, but volunteers only if policy allows
                if conference.twitter_postpolicy != 3:
                    raise PermissionDenied()

            try:
                t = ConferenceTweetQueue.objects.get(conference=conference,
                                                     approved=False,
                                                     pk=get_int_or_error(
                                                         request.POST, 'id'))
            except ConferenceTweetQueue.DoesNotExist:
                return _json_response({'error': 'Tweet already discarded'})
            if t.approved:
                return _json_response(
                    {'error': 'Tweet has already been approved'})

            if request.POST.get('op') == 'approve':
                if t.author == reg.attendee:
                    return _json_response(
                        {'error': "Can't approve your own tweets"})

                t.approved = True
                t.approvedby = reg.attendee
                t.save()
                trigger_immediate_job_run('twitter_post')
            else:
                t.delete()
            return _json_response({})
        elif request.POST.get('op', None) in ('dismissincoming', 'retweet'):
            if not is_admin:
                # Admins can always approve, but volunteers only if policy allows
                if conference.twitter_postpolicy != 3:
                    raise PermissionDenied()

            try:
                t = ConferenceIncomingTweet.objects.get(
                    conference=conference,
                    statusid=get_int_or_error(request.POST, 'id'))
            except ConferenceIncomingTweet.DoesNotExist:
                return _json_response({'error': 'Tweet does not exist'})

            if request.POST.get('op', None) == 'dismissincoming':
                if t.processedat:
                    return _json_response(
                        {'error': 'Tweet is already dismissed or replied'})

                t.processedby = reg.attendee
                t.processedat = timezone.now()
                t.save(update_fields=['processedby', 'processedat'])
            else:
                if t.retweetstate > 0:
                    return _json_response({'error': 'Tweet '})
                t.retweetstate = 1
                t.save(update_fields=['retweetstate'])
                trigger_immediate_job_run('twitter_post')

            return _json_response({})
        else:
            # Unknown op
            raise Http404()

    # GET request here
    if request.GET.get('op', None) == 'queue':
        # We show the queue to everybody, but non-moderators don't get to approve

        # Return the approval queue
        queue = ConferenceTweetQueue.objects.defer(
            'image',
            'imagethumb').filter(conference=conference, approved=False).extra(
                select={
                    'hasimage': "image is not null and image != ''"
                }).order_by('datetime')

        # Return the latest ones approved
        latest = ConferenceTweetQueue.objects.defer(
            'image',
            'imagethumb').filter(conference=conference, approved=True).extra(
                select={
                    'hasimage': "image is not null and image != ''"
                }).order_by('-datetime')[:5]

        def _postdata(objs):
            return [{
                'id': t.id,
                'txt': t.contents,
                'author': t.author and t.author.username or '',
                'time': t.datetime,
                'hasimage': t.hasimage,
                'delivered': t.sent,
            } for t in objs]

        return _json_response({
            'queue': _postdata(queue),
            'latest': _postdata(latest),
        })
    elif request.GET.get('op', None) == 'incoming':
        incoming = ConferenceIncomingTweet.objects.select_related(
            'provider').filter(conference=conference,
                               processedat__isnull=True).order_by('created')
        latest = ConferenceIncomingTweet.objects.select_related(
            'provider').filter(
                conference=conference,
                processedat__isnull=False).order_by('-processedat')[:5]

        def _postdata(objs):
            return [{
                'id':
                str(t.statusid),
                'txt':
                t.text,
                'author':
                t.author_screenname,
                'authorfullname':
                t.author_name,
                'time':
                t.created,
                'rt':
                t.retweetstate,
                'provider':
                t.provider.publicname,
                'media': [m for m in t.media if m is not None],
                'url':
                providers.get(t.provider).get_public_url(t),
                'replymaxlength':
                providers.get(t.provider).max_post_length,
            } for t in objs.annotate(
                media=ArrayAgg('conferenceincomingtweetmedia__mediaurl'))]

        return _json_response({
            'incoming': _postdata(incoming),
            'incominglatest': _postdata(latest),
        })
    elif request.GET.get('op', None) == 'hasqueue':
        return _json_response({
            'hasqueue':
            ConferenceTweetQueue.objects.filter(
                conference=conference,
                approved=False).exclude(author=reg.attendee_id).exists(),
            'hasincoming':
            ConferenceIncomingTweet.objects.filter(
                conference=conference, processedat__isnull=True).exists(),
        })
    elif request.GET.get('op', None) == 'thumb':
        # Get a thumbnail -- or make one if it's not there
        t = get_object_or_404(ConferenceTweetQueue,
                              conference=conference,
                              pk=get_int_or_error(request.GET, 'id'))
        if not t.imagethumb:
            # Need to generate a thumbnail here. Thumbnails are always made in PNG!
            p = ImageFile.Parser()
            p.feed(bytes(t.image))
            p.close()
            im = p.image
            im.thumbnail((256, 256))
            b = io.BytesIO()
            im.save(b, "png")
            t.imagethumb = b.getvalue()
            t.save()

        resp = HttpResponse(content_type='image/png')
        resp.write(bytes(t.imagethumb))
        return resp

    # Maximum length from any of the configured providers
    providermaxlength = {
        m.provider.publicname: providers.get(m.provider).max_post_length
        for m in ConferenceMessaging.objects.select_related('provider').filter(
            conference=conference, broadcast=True, provider__active=True)
    }

    return render(
        request, 'confreg/twitter.html', {
            'conference':
            conference,
            'reg':
            reg,
            'poster':
            canpost and 1 or 0,
            'directposter':
            canpostdirect and 1 or 0,
            'moderator':
            canmoderate and 1 or 0,
            'providerlengths':
            ", ".join(
                ["{}: {}".format(k, v) for k, v in providermaxlength.items()]),
            'maxlength':
            max((v for k, v in providermaxlength.items())),
        })
Exemplo n.º 14
0
def report(request, year, reporttype):
    authenticate_backend_group(request, 'Accounting managers')

    years = list(Year.objects.all())
    year = get_object_or_404(Year, year=year)

    mindate = maxdate = None
    suppress_years = False

    if request.GET.get('obj', None):
        object = get_object_or_404(Object,
                                   pk=get_int_or_error(request.GET, 'obj'))
        objstr = "AND ji.object_id=%s" % object.id
    else:
        object = None
        objstr = ''

    hasopenentries = JournalEntry.objects.filter(year=year,
                                                 closed=False).exists()

    if request.GET.get('acc', None):
        account = get_object_or_404(Account,
                                    num=get_int_or_error(request.GET, 'acc'))
    else:
        account = None

    if request.GET.get('ed', None) and request.GET['ed'] != 'undefined':
        enddate = datetime.strptime(request.GET['ed'], '%Y-%m-%d').date()
        if year and enddate.year != year.year:
            enddate = date(year.year, 12, 31)
    else:
        enddate = date(year.year, 12, 31)

    if request.GET.get('io', 0) == '1':
        includeopen = True
    else:
        includeopen = False

    filtered_objects = _get_reportable_objects(year)

    if reporttype == 'ledger':
        # This is a special report, so we don't use the collate functionality
        # XXX: consider perhaps including the in and out balance as well.

        # Yup, the django ORM fails again - no window aggregates
        sql = "SELECT a.num as accountnum, a.name as accountname, sum(i.amount) FILTER (WHERE i.amount > 0) over w1 as totaldebit, sum(-i.amount) FILTER (WHERE i.amount < 0) over w1 as totalcredit, e.seq as entryseq, e.date, i.description, case when i.amount > 0 then i.amount else 0 end as debit, case when i.amount < 0 then -i.amount else 0 end as credit, o.name as object, e.closed FROM accounting_journalitem i INNER JOIN accounting_account a ON i.account_id=a.num INNER JOIN accounting_journalentry e ON i.journal_id=e.id LEFT JOIN accounting_object o ON i.object_id=o.id WHERE e.year_id=%(year)s AND (e.closed OR %(includeopen)s) AND e.date<=%(enddate)s"
        params = {
            'year': year.year,
            'enddate': enddate,
            'includeopen': includeopen,
        }
        if request.GET.get('obj', None):
            sql += " AND o.id=%(objectid)s"
            params['objectid'] = get_int_or_error(request.GET, 'obj')
        if request.GET.get('acc', None):
            sql += " AND a.num=%(account)s"
            params['account'] = get_int_or_error(request.GET, 'acc')
        sql += " WINDOW w1 AS (PARTITION BY a.num) ORDER BY a.num, e.date, e.seq"
        curs = connection.cursor()
        curs.execute(sql, params)

        # Django templates are also too stupid to be able to produce
        # a section-summary value, so we need to build them up as
        # a two stage array.
        items = []
        lastaccount = 0
        for row in curs.fetchall():
            if row[0] != lastaccount:
                items.append({
                    'accountnum': row[0],
                    'accountname': row[1],
                    'totaldebit': row[2],
                    'totalcredit': row[3],
                    'entries': []
                })
                lastaccount = row[0]
            items[-1]['entries'].append(
                dict(
                    list(zip([col[0] for col in curs.description[4:]],
                             row[4:]))))

        return render(
            request, 'accounting/ledgerreport.html', {
                'year': year,
                'years': years,
                'reportable_objects': filtered_objects,
                'currentobj': object,
                'accounts': Account.objects.all(),
                'currentaccount': account,
                'reporttype': 'ledger',
                'title': 'Ledger',
                'items': items,
                'enddate': enddate,
                'hasopenentries': hasopenentries,
                'includeopen': includeopen,
                'yearsuffix': 'report/ledger/',
                'isreport': True,
            })
    elif reporttype == 'results':
        # The results report is the easiest one, since we can assume that
        # all accounts enter the year with a value 0. Therefor, we only
        # care about summing the data for this year.
        # We only show accounts that have had some transactions on them.
        # Note! Probably sync up much of this query with the object report!
        (results, totalresult) = _collate_results(
            """WITH t AS (
SELECT ac.name as acname,
       ag.name as agname,
       ag.foldable,
       a.num as anum,
       a.name,
       sum(-ji.amount) as amount
FROM accounting_accountclass ac
INNER JOIN accounting_accountgroup ag ON ac.id=ag.accountclass_id
INNER JOIN accounting_account a ON ag.id=a.group_id
INNER JOIN accounting_journalitem ji ON ji.account_id=a.num
INNER JOIN accounting_journalentry je ON je.id=ji.journal_id
WHERE je.year_id=%(year)s
  AND je.date <= %(enddate)s
  AND (je.closed or %(includeopen)s)
  AND NOT ac.inbalance
{0}
GROUP BY ac.name, ag.name, ag.foldable, a.id, a.name
)
SELECT acname,
       agname,
       anum,
       name,
       count(*) OVER (PARTITION BY agname) = 1 AND foldable as agfold,
       sum(amount) OVER (PARTITION by acname) AS acamount,
       sum(amount) OVER (PARTITION by agname) AS agamount,
       amount,
       sum(amount) OVER ()
FROM t
ORDER BY anum""".format(objstr), {
                'year': year.year,
                'enddate': enddate,
                'includeopen': includeopen,
            }, 1)
        title = 'Results report'
        totalname = 'Final result'
        valheaders = ['Amount']
    elif reporttype == 'balance':
        # Balance report.
        # We always assume we have an incoming balance and that the previous
        # year has been closed. If it's not closed, we just show a warning
        # about that.
        try:
            prevyear = Year.objects.get(year=year.year - 1)
            if prevyear and prevyear.isopen:
                messages.warning(
                    request,
                    'Previous year (%s) is still open. Incoming balance will be incorrect!'
                    % prevyear.year)
        except Year.DoesNotExist:
            pass

        if includeopen:
            valheaders = [
                'Incoming', 'Period', 'Open', 'Period+Open', 'Outgoing'
            ]
        else:
            valheaders = ['Incoming', 'Period', 'Outgoing']
        (results, totalresult) = _collate_results(
            _get_balance_query(objstr, includeopen), {
                'year': year.year,
                'enddate': enddate,
            }, len(valheaders))
        title = 'Balance report'
        totalname = 'Final balance'
    elif reporttype == 'object':
        if not object:
            raise Http404("Object report requires object")

        # Hasopenentries now need to reflect if this *object* has any open entries
        hasopenentries = JournalEntry.objects.filter(
            journalitem__object=object, closed=False).exists()

        # Note! Probably sync up much of this query with the results report!
        (results, totalresult) = _collate_results(
            """WITH t AS (
SELECT ac.name as acname,
       ag.name as agname,
       ag.foldable,
       a.num as anum,
       a.name,
       sum(-ji.amount) as amount
FROM accounting_accountclass ac
INNER JOIN accounting_accountgroup ag ON ac.id=ag.accountclass_id
INNER JOIN accounting_account a ON ag.id=a.group_id
INNER JOIN accounting_journalitem ji ON ji.account_id=a.num
INNER JOIN accounting_journalentry je ON je.id=ji.journal_id
WHERE (je.closed or %(includeopen)s)
  AND NOT ac.inbalance {0}
GROUP BY ac.name, ag.name, ag.foldable, a.id, a.name
)
SELECT acname,
       agname,
       anum,
       name,
       count(*) OVER (PARTITION BY agname) = 1 AND foldable as agfold,
       sum(amount) OVER (PARTITION by acname) AS acamount,
       sum(amount) OVER (PARTITION by agname) AS agamount,
       amount,
       sum(amount) OVER ()
FROM t
ORDER BY anum""".format(objstr), {
                'year': year.year,
                'enddate': enddate,
                'includeopen': includeopen,
            }, 1)
        title = '{0} result report'.format(object.name)
        totalname = 'Final result for {0}'.format(object.name)
        valheaders = ['Amount']

        curs = connection.cursor()
        curs.execute(
            "SELECT min(date), max(date) FROM accounting_journalentry e WHERE EXISTS (SELECT 1 FROM accounting_journalitem i WHERE i.object_id=%(object)s AND i.journal_id=e.id)",
            {
                'object': object.id,
            })
        (mindate, maxdate) = curs.fetchone()

        suppress_years = True
    else:
        raise Http404("Unknown report")

    # XXX: PDF maybe?
    return render(
        request, 'accounting/yearreports.html', {
            'reporttype': reporttype,
            'title': title,
            'year': year and year or -1,
            'years': years,
            'reportable_objects': filtered_objects,
            'hasopenentries': hasopenentries,
            'results': results,
            'totalresult': totalresult,
            'valheaders': valheaders,
            'totalname': totalname,
            'currentobj': object,
            'enddate': enddate,
            'mindate': mindate,
            'maxdate': maxdate,
            'includeopen': includeopen,
            'yearsuffix': 'report/{}/'.format(reporttype),
            'isreport': True,
            'suppress_years': suppress_years,
        })
Exemplo n.º 15
0
def entry(request, entryid):
    authenticate_backend_group(request, 'Accounting managers')

    entry = get_object_or_404(JournalEntry, pk=entryid)

    if 'search' in request.GET:
        _setup_search(request, request.GET['search'])
        return HttpResponseRedirect('/accounting/e/%s/' % entryid)

    (searchterm, entries) = _perform_search(request, entry.year)

    paginator = EntryPaginator(entries)
    currpage = get_int_or_error(request.GET, 'p', 1)

    extra = max(2, 6 - entry.journalitem_set.count())
    inlineformset = inlineformset_factory(JournalEntry,
                                          JournalItem,
                                          JournalItemForm,
                                          JournalItemFormset,
                                          can_delete=True,
                                          extra=extra)
    inlineurlformset = inlineformset_factory(JournalEntry,
                                             JournalUrl,
                                             JournalUrlForm,
                                             can_delete=True,
                                             extra=2,
                                             exclude=[])

    if request.method == 'POST':
        if request.POST['submit'] == 'Delete':
            year = entry.year
            entry.delete()
            return HttpResponseRedirect("/accounting/%s/" % year.year)

        form = JournalEntryForm(data=request.POST, instance=entry)
        formset = inlineformset(data=request.POST, instance=entry)
        urlformset = inlineurlformset(data=request.POST, instance=entry)

        if form.is_valid():
            if formset.is_valid() and urlformset.is_valid():
                instance = form.save()
                formset.save()
                urlformset.save()

                if request.POST['submit'] == 'Close':
                    instance.closed = True
                    instance.save()
                return HttpResponseRedirect(".")
        # Else fall through
    else:
        form = JournalEntryForm(instance=entry)
        formset = inlineformset(instance=entry)
        urlformset = inlineurlformset(instance=entry)

    items = list(entry.journalitem_set.all())
    totals = (sum([i.amount for i in items if i.amount > 0]),
              -sum([i.amount for i in items if i.amount < 0]))
    urls = list(entry.journalurl_set.all())
    return render(
        request, 'accounting/main.html', {
            'entries': paginator.page(currpage),
            'page': currpage,
            'pages': paginator.get_pages(currpage),
            'numpages': paginator.num_pages,
            'hasopen': any([not e.closed for e in entries]),
            'year': entry.year,
            'entry': entry,
            'has_pending_banktrans': entry.pendingbankmatcher_set.exists(),
            'items': items,
            'urls': urls,
            'totals': totals,
            'form': form,
            'formset': formset,
            'urlformset': urlformset,
            'years': Year.objects.all(),
            'reportable_objects': _get_reportable_objects(entry.year),
            'searchterm': searchterm,
        })
Exemplo n.º 16
0
def banktransactions(request):
    authenticate_backend_group(request, 'Invoice managers')

    if request.method == 'POST':
        if 'submit' not in request.POST:
            return HttpResponseRedirect(".")

        if 'transid' in request.POST:
            trans = get_object_or_404(PendingBankTransaction,
                                      id=get_int_or_error(
                                          request.POST, 'transid'))

            if request.POST['submit'] == 'Discard':
                InvoiceLog(
                    message="Discarded bank transaction of {0}{1} with text {2}"
                    .format(trans.amount, settings.CURRENCY_ABBREV,
                            trans.transtext)).save()

                trans.delete()

                messages.info(request, "Transaction discarded")
                return HttpResponseRedirect(".")
            elif request.POST['submit'] == 'Create accounting record':
                pm = trans.method.get_implementation()

                accrows = [
                    (pm.config('bankaccount'), trans.transtext, trans.amount,
                     None),
                ]
                entry = create_accounting_entry(date.today(), accrows, True)

                InvoiceLog(
                    message=
                    "Created manual accounting entry for transaction of {0}{1} with text {2}"
                    .format(trans.amount, settings.CURRENCY_ABBREV,
                            trans.transtext)).save()

                trans.delete()

                return HttpResponseRedirect("/accounting/e/{0}/".format(
                    entry.id))
            elif request.POST['submit'] == 'Return to sender':
                pm = trans.method.get_implementation()

                pm.return_payment(trans)

                InvoiceLog(
                    message=
                    "Scheduled transaction '{0}' ({1}{2}) for return to sender using {3}"
                    .format(trans.transtext, trans.amount,
                            settings.CURRENCY_ABBREV,
                            trans.method.internaldescription)).save()
                trans.delete()

                return HttpResponseRedirect(".")
            else:
                raise Http404("Invalid request")
        elif 'matcherid' in request.POST:
            matcher = get_object_or_404(PendingBankMatcher,
                                        pk=get_int_or_error(
                                            request.POST, 'matcherid'))
            if request.POST['submit'] == 'Discard':
                InvoiceLog(
                    message="Discarded pending bank matcher {0} for {1} {2}".
                    format(matcher.pattern, matcher.amount,
                           settings.CURRENCY_ABBREV)).save()

                matcher.delete()

                messages.info(request, "Matcher discarded")
                return HttpResponseRedirect(".")
            else:
                raise Http404("Invalid request")
        else:
            raise Http404("Invalid request")

    pendingtransactions = PendingBankTransaction.objects.order_by('created')
    pendingmatchers = PendingBankMatcher.objects.order_by('created')

    return render(
        request, 'invoices/banktransactions.html', {
            'transactions': pendingtransactions,
            'matchers': pendingmatchers,
            'topadmin': 'Invoices',
            'helplink': 'payment',
        })
Exemplo n.º 17
0
def bankfiles(request):
    authenticate_backend_group(request, 'Invoice managers')

    if request.method == 'POST':
        # Uploading a file!
        method = get_object_or_404(InvoicePaymentMethod,
                                   active=True,
                                   config__has_key='file_upload_interval',
                                   id=get_int_or_error(request.POST, 'id'))

        impl = method.get_implementation()
        # Stage 1 upload has the file in request.FILES. Stage 2 has it in a hidden field in the form instead,
        # because we can't make it upload th file twice.
        if 'fc' in request.POST:
            # In stage 2 the file is included ase base64
            txt = impl.convert_uploaded_file_to_utf8(
                BytesIO(base64.b64decode(request.POST['fc'])))

            try:
                rows = impl.parse_uploaded_file_to_rows(txt)
                (anyerror, extrakeys,
                 hasvaluefor) = impl.process_loaded_rows(rows)

                numrows = len(rows)
                numtrans = 0
                numpending = 0
                numerrors = 0

                with transaction.atomic():
                    # Store thef file itself
                    bankfile = BankFileUpload(
                        method=method,
                        parsedrows=numrows,
                        newtrans=0,
                        newpending=0,
                        errors=0,
                        uploadby=request.user.username,
                        name=request.POST['name'],
                        textcontents=txt,
                    )
                    bankfile.save()  # To get an id we can use

                    for r in rows:
                        if r['row_already_exists']:
                            continue
                        if r.get('row_errors', []):
                            numerrors += 1
                            continue

                        # Insert the row
                        b = BankStatementRow(
                            method=method,
                            fromfile=bankfile,
                            uniqueid=r.get('uniqueid', None),
                            date=r['date'],
                            amount=r['amount'],
                            description=r['text'],
                            balance=r.get('balance', None),
                            other=r['other'],
                        )
                        b.save()
                        numtrans += 1

                        if not register_bank_transaction(
                                b.method, b.id, b.amount, b.description, ''):
                            # This means the transaction wasn't directly matched and has been
                            # registered as a pending transaction.
                            numpending += 1

                    bankfile.newtrans = numtrans
                    bankfile.newpending = numpending
                    bankfile.errors = numerrors
                    bankfile.save()
            except Exception as e:
                messages.error(request, "Error uploading file: {}".format(e))

            return HttpResponseRedirect(".")

        if 'f' not in request.FILES:
            messages.error(request, "No file included in upload")
        elif request.FILES['f'].size < 1:
            messages.error(request, "Uploaded file is empty")
        else:
            f = request.FILES['f']

            try:
                # Stage 1, mean we parse it and render a second form to confirm
                rows = impl.parse_uploaded_file_to_rows(
                    impl.convert_uploaded_file_to_utf8(f))
                (anyerror, extrakeys,
                 hasvaluefor) = impl.process_loaded_rows(rows)

                f.seek(0)

                return render(
                    request, 'invoices/bankfile_uploaded.html', {
                        'method': method,
                        'rows': rows,
                        'extrakeys': sorted(extrakeys),
                        'hasvaluefor': hasvaluefor,
                        'anyerror': anyerror,
                        'filename': f.name,
                        'fc': base64.b64encode(f.read()).decode('ascii'),
                        'topadmin': 'Invoices',
                        'helplink': 'payment',
                        'breadcrumbs': [
                            ('../../', 'Bank files'),
                        ],
                    })
            except Exception as e:
                messages.error(request, "Error uploading file: {}".format(e))

    methods = InvoicePaymentMethod.objects.filter(
        active=True, config__has_key='file_upload_interval').annotate(
            latest_file=Max('bankfileupload__created'))
    file_objects = BankFileUpload.objects.select_related(
        'method').all().order_by('-created')[:1000]
    (files, paginator, page_range) = simple_pagination(request, file_objects,
                                                       50)

    return render(
        request, 'invoices/bankfiles.html', {
            'files': files,
            'page_range': page_range,
            'methods': methods,
            'topadmin': 'Invoices',
            'helplink': 'payment',
        })
Exemplo n.º 18
0
def payment_post(request):
    nonce = request.POST['payment_method_nonce']
    invoice = get_object_or_404(Invoice, pk=get_int_or_error(request.POST, 'invoice'), deleted=False, finalized=True)
    method = get_object_or_404(InvoicePaymentMethod, pk=get_int_or_error(request.POST, 'method'), active=True)
    pm = method.get_implementation()

    if invoice.processor:
        manager = InvoiceManager()
        processor = manager.get_invoice_processor(invoice)
        returnurl = processor.get_return_url(invoice)
    else:
        if invoice.recipient_user:
            returnurl = "%s/invoices/%s/" % (settings.SITEBASE, invoice.pk)
        else:
            returnurl = "%s/invoices/%s/%s/" % (settings.SITEBASE, invoice.pk, invoice.recipient_secret)

    # Generate the transaction
    result = pm.braintree_sale({
        'amount': '{0}'.format(invoice.total_amount),
        'order_id': '#{0}'.format(invoice.pk),
        'payment_method_nonce': nonce,
        'merchant_account_id': pm.config('merchantacctid'),
        'options': {
            'submit_for_settlement': True,
        }
    })

    trans = result.transaction
    if result.is_success:
        # Successful transaction. Store it for later processing. At authorization, we proceed to
        # flag the payment as done.

        BraintreeLog(transid=trans.id,
                     message='Received successful result for {0}'.format(trans.id),
                     paymentmethod=method).save()

        if trans.currency_iso_code != settings.CURRENCY_ISO:
            BraintreeLog(transid=trans.id,
                         error=True,
                         message='Invalid currency {0}, should be {1}'.format(trans.currency_iso_code, settings.CURRENCY_ISO),
                         paymentmethod=method).save()

            send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                             pm.config('notification_receiver'),
                             'Invalid currency received in Braintree payment',
                             'Transaction {0} paid in {1}, should be {2}.'.format(trans.id, trans.currency_iso_code, settings.CURRENCY_ISO))

            # We'll just throw the "processing error" page, and have
            # the operator deal with the complaints as this is a
            # should-never-happen scenario.
            return render(request, 'braintreepayment/processing_error.html')

        with transaction.atomic():
            # Flag the invoice as paid
            manager = InvoiceManager()
            try:
                def invoice_logger(msg):
                    raise BraintreeProcessingException('Invoice processing failed: %s'.format(msg))

                manager.process_incoming_payment_for_invoice(invoice,
                                                             trans.amount,
                                                             'Braintree id {0}'.format(trans.id),
                                                             0,
                                                             pm.config('accounting_authorized'),
                                                             0,
                                                             [],
                                                             invoice_logger,
                                                             method,
                                                         )
            except BraintreeProcessingException as ex:
                send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                                 pm.config('notification_receiver'),
                                 'Exception occurred processing Braintree result',
                                 "An exception occured processing the payment result for {0}:\n\n{1}\n".format(trans.id, ex))

                return render(request, 'braintreepayment/processing_error.html')

            # Create a braintree transaction - so we can update it later when the transaction settles
            bt = BraintreeTransaction(transid=trans.id,
                                      authorizedat=timezone.now(),
                                      amount=trans.amount,
                                      method=trans.credit_card['card_type'],
                                      paymentmethod=method)
            if invoice.accounting_object:
                bt.accounting_object = invoice.accounting_object
            bt.save()

            send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                             pm.config('notification_receiver'),
                             'Braintree payment authorized',
                             "A payment of %s%s with reference %s was authorized on the Braintree platform for %s.\nInvoice: %s\nRecipient name: %s\nRecipient user: %s\nBraintree reference: %s\n" % (
                                 settings.CURRENCY_ABBREV,
                                 trans.amount,
                                 trans.id,
                                 method.internaldescription,
                                 invoice.title,
                                 invoice.recipient_name,
                                 invoice.recipient_email,
                                 trans.id))

        return HttpResponseRedirect(returnurl)
    else:
        if not trans:
            reason = "Internal error"
        elif trans.status == 'processor_declined':
            reason = "Processor declined: {0}/{1}".format(trans.processor_response_code, trans.processor_response_text)
        elif trans.status == 'gateway_rejected':
            reason = "Gateway rejected: {0}".format(trans.gateway_rejection_reason)
        else:
            reason = "unknown"
        BraintreeLog(transid=trans and trans.id or "UNKNOWN",
                     message='Received FAILED result for {0}'.format(trans and trans.id or "UNKNOWN"),
                     error=True, paymentmethod=method).save()

        return render(request, 'braintreepayment/payment_failed.html', {
            'invoice': invoice,
            'reason': reason,
            'url': returnurl,
        })