NextCloud Integration
This commit is contained in:
parent
02ad7a9441
commit
5f4b10cb82
113 changed files with 18992 additions and 235 deletions
8
uncloud_pay/forms.py
Normal file
8
uncloud_pay/forms.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from uncloud.forms import MainModelForm
|
||||
from .models import BillingAddress
|
||||
|
||||
class BillingAddressForm(MainModelForm):
|
||||
class Meta:
|
||||
model = BillingAddress
|
||||
fields = ['full_name', 'street',
|
||||
'city', 'postal_code', 'country', 'vat_number', 'active', 'owner']
|
||||
|
|
@ -13,8 +13,6 @@ from django.db import models
|
|||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django_q.tasks import schedule
|
||||
from django_q.models import Schedule
|
||||
# Verify whether or not to use them here
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
|
||||
|
|
@ -722,7 +720,7 @@ class Order(models.Model):
|
|||
|
||||
if self.recurring_period.duration_seconds > 0:
|
||||
delta = until_when - self.starting_date
|
||||
|
||||
|
||||
num_times = ceil(delta.total_seconds() / self.recurring_period.duration_seconds)
|
||||
|
||||
next_date = self.starting_date + datetime.timedelta(seconds=num_times * self.recurring_period.duration_seconds)
|
||||
|
|
@ -762,12 +760,6 @@ class Order(models.Model):
|
|||
self.ending_date = timezone.now()
|
||||
self.should_be_billed = False
|
||||
self.save()
|
||||
if hasattr(self, 'instance_id'):
|
||||
last_bill_record = BillRecord.objects.filter(order=self).order_by('id').last()
|
||||
schedule('matrixhosting.tasks.delete_instance',
|
||||
self.instance_id,
|
||||
schedule_type=Schedule.ONCE,
|
||||
next_run=last_bill_record.ending_date or (timezone.now() + datetime.timedelta(hours=1)))
|
||||
|
||||
def count_used(self, when=None):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ def get_spendings_for_user(user):
|
|||
|
||||
@transaction.atomic
|
||||
def get_balance_for_user(user):
|
||||
return get_deposit_payments_for_user(user) - get_spendings_for_user(user)
|
||||
return float(get_deposit_payments_for_user(user) - get_spendings_for_user(user))
|
||||
|
||||
@transaction.atomic
|
||||
def has_enough_balance(user, due_amount):
|
||||
|
|
|
|||
114
uncloud_pay/static/uncloud_pay/css/invoice.css
Normal file
114
uncloud_pay/static/uncloud_pay/css/invoice.css
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
body {
|
||||
font-family: Avenir;
|
||||
background: white;
|
||||
font-weight: 500;
|
||||
line-height: 1.1em;
|
||||
font-size: 16px;
|
||||
margin: auto;
|
||||
}
|
||||
p {
|
||||
display: block;
|
||||
-webkit-margin-before: 14px;
|
||||
-webkit-margin-after: 14px;
|
||||
-webkit-margin-start: 0px;
|
||||
-webkit-margin-end: 0px;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.d1 {
|
||||
line-height:1.1em;
|
||||
width: 60%;
|
||||
float: left;
|
||||
}
|
||||
.d2 {
|
||||
line-height:1.5em;
|
||||
padding-top: 15px;
|
||||
font-style: normal;
|
||||
width: 40%;
|
||||
float: left;
|
||||
}
|
||||
.d4 {
|
||||
line-height:1.5em;
|
||||
width:40%;
|
||||
float: left;
|
||||
}
|
||||
.b1 {
|
||||
width: 45%;
|
||||
float: left;
|
||||
}
|
||||
.b2 {
|
||||
width: 55%;
|
||||
float: left;
|
||||
text-align: right;
|
||||
left: 0;
|
||||
}
|
||||
.d5 {
|
||||
width: 100%;
|
||||
}
|
||||
.d6 {
|
||||
width: 68%;
|
||||
float: left;
|
||||
font-size: 13px;
|
||||
}
|
||||
.d7 {
|
||||
width: 32%;
|
||||
float: left;
|
||||
}
|
||||
.wf {
|
||||
width: 100%;
|
||||
}
|
||||
hr {
|
||||
border: 0;
|
||||
clear:both;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background-color:gray;
|
||||
height: 1px;
|
||||
}
|
||||
.tl {
|
||||
text-align: left;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.tr {
|
||||
text-align: right;
|
||||
margin-right: 5px;
|
||||
float: right;
|
||||
}
|
||||
.tc {
|
||||
text-align: center;
|
||||
}
|
||||
.pc p {
|
||||
display: block;
|
||||
-webkit-margin-before: 3px;
|
||||
-webkit-margin-after: 5px;
|
||||
-webkit-margin-start: 0px;
|
||||
-webkit-margin-end: 0px;
|
||||
}
|
||||
.th {
|
||||
border-top: 1px solid gray;
|
||||
border-bottom: 1px solid gray;
|
||||
|
||||
}
|
||||
.ts {
|
||||
font-size: 14px;
|
||||
}
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 70px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
display: block;
|
||||
-webkit-margin-before: 5px;
|
||||
-webkit-margin-after: 5px;
|
||||
-webkit-margin-start: 0px;
|
||||
-webkit-margin-end: 0px;
|
||||
}
|
||||
BIN
uncloud_pay/static/uncloud_pay/images/call.png
Normal file
BIN
uncloud_pay/static/uncloud_pay/images/call.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
BIN
uncloud_pay/static/uncloud_pay/images/company-small.jpg
Normal file
BIN
uncloud_pay/static/uncloud_pay/images/company-small.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
uncloud_pay/static/uncloud_pay/images/home.png
Normal file
BIN
uncloud_pay/static/uncloud_pay/images/home.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
uncloud_pay/static/uncloud_pay/images/msg.png
Normal file
BIN
uncloud_pay/static/uncloud_pay/images/msg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
BIN
uncloud_pay/static/uncloud_pay/images/twitter.png
Normal file
BIN
uncloud_pay/static/uncloud_pay/images/twitter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
BIN
uncloud_pay/static/uncloud_pay/webfonts/Avenir-Book.ttf
Normal file
BIN
uncloud_pay/static/uncloud_pay/webfonts/Avenir-Book.ttf
Normal file
Binary file not shown.
33
uncloud_pay/templates/uncloud_pay/includes/_card.html
Normal file
33
uncloud_pay/templates/uncloud_pay/includes/_card.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{% load i18n %}
|
||||
<form action="" id="payment-form-new" method="POST">
|
||||
<input type="hidden" name="token"/>
|
||||
<input type="hidden" name="id_card" id="id_card" value=""/>
|
||||
<div class="group">
|
||||
<div class="credit-card-goup">
|
||||
<div class="card-element card-number-element form-group">
|
||||
<label>{%trans "Card Number" %}</label>
|
||||
<div id="card-number-element" class="field my-input form-control"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card-element card-expiry-element col-md-6 form-group">
|
||||
<label>{%trans "Expiry Date" %}</label>
|
||||
<div id="card-expiry-element" class="field my-input form-control"></div>
|
||||
</div>
|
||||
<div class="card-element card-cvc-element col-md-6">
|
||||
<label>{%trans "CVC" %}</label>
|
||||
<div id="card-cvc-element" class="field my-input form-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-element brand text-6">
|
||||
<i class="fab fa-credit-card" id="brand-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="card-errors"></div>
|
||||
<div class="text-right">
|
||||
<button class="btn btn-vm-contact btn-primary btn-wide" type="submit" name="payment-form">{%trans "Checkout" %}</button>
|
||||
</div>
|
||||
<div style="display:none;">
|
||||
<p class="payment-errors"></p>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
{% load static i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Avenir;
|
||||
font-weight: 500;
|
||||
line-height: 1.1em;
|
||||
font-size: 16px;
|
||||
}
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 70px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
display: block;
|
||||
-webkit-margin-before: 5px;
|
||||
-webkit-margin-after: 5px;
|
||||
-webkit-margin-start: 0px;
|
||||
-webkit-margin-end: 0px;
|
||||
}
|
||||
.d6 {
|
||||
width: 68%;
|
||||
float: left;
|
||||
font-size: 13px;
|
||||
}
|
||||
.wf {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="footer wf custom_footer">
|
||||
<br/>
|
||||
<div class="d6">
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/call.png' %}"/>
|
||||
<span>+41 55 505 6266</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/msg.png' %}"/>
|
||||
<span>buchhaltung-ag@ungleich.ch</span>
|
||||
</p>
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/home.png' %}"/>
|
||||
<span>https://www.ungleich.ch</span>
|
||||
</p>
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/twitter.png' %}"/>
|
||||
@ungleich
|
||||
</p>
|
||||
</div>
|
||||
<div class="d7">
|
||||
<div>
|
||||
<p>Glarner Kantonalbank</p>
|
||||
<p>
|
||||
<span class="bold">IBAN: CH 4300 7730 0055 5931 177</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="bold">BIC: GLKBCH22</span>
|
||||
</p>
|
||||
</div>
|
||||
<p style="font-size: 13px; white-space: nowrap !important">Mwst-Nummer: CHE-156.970.649 MWST</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
129
uncloud_pay/templates/uncloud_pay/invoice.html
Normal file
129
uncloud_pay/templates/uncloud_pay/invoice.html
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
{% load static i18n %}
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{%trans "Invoice: " %} {{bill.id}}</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Avenir';
|
||||
src: url("{{ base_url }}{% static 'uncloud_pay/webfonts/Avenir-Book.ttf' %}");
|
||||
}
|
||||
</style>
|
||||
<link href="{{ base_url }}{% static 'uncloud_pay/css/invoice.css' %}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="invoice-container">
|
||||
|
||||
<div class="header" style="line-height:1.1em;">
|
||||
<div class="d1">
|
||||
<div class="logo">
|
||||
<img class="" src="{{ base_url }}{% static 'uncloud_pay/images/company-small.jpg' %}" width="50%"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d2" name="company_address">
|
||||
<span class="bold">ungleich glarus ag</span>
|
||||
<address style="font-style: normal;">
|
||||
Bahnhofstrasse 1<br>
|
||||
8783 Linthal<br>
|
||||
Switzerland
|
||||
</address>
|
||||
</div>
|
||||
<div class="first-page">
|
||||
|
||||
<div class="d1">
|
||||
<b>
|
||||
<span style="font-size: 16px">{{bill.billing_address.full_name}}</span>
|
||||
</b>
|
||||
<br/>
|
||||
<span>{{bill.billing_address.owner.email}}</span>
|
||||
<address style="font-style: normal;">
|
||||
{{bill.billing_address.street}}<br>
|
||||
{{bill.billing_address.city}}<br>
|
||||
{{bill.billing_address.get_country_display}}
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d4" style="margin-top:15px;">
|
||||
<div class="bold">
|
||||
<div class="b1">
|
||||
<span>{%trans "Bill Number:" %}</span>
|
||||
</div>
|
||||
<div class="b2">
|
||||
<span>#{{bill.id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="b1">
|
||||
<span>{%trans "Date of invoice:" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="b2">
|
||||
<span>{{bill.starting_date|date:"Y-m-d"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="b1">
|
||||
<span>{%trans "Due Date:" %}</span>
|
||||
</div>
|
||||
<div class="b2">
|
||||
<span>{{bill.due_date|date:"Y-m-d"}}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: both;">
|
||||
</div>
|
||||
<div style="clear: both;">
|
||||
<table class="wf" style="margin-top:20px; margin-bottom:10px important;border-top: 1px solid gray;">
|
||||
<thead >
|
||||
<tr class="" style="padding-top: 10px; padding-bottom:10px;background-color: #f5f5f5;">
|
||||
<th class="tl bold" style="padding: 5px 0px 5px 0px !important;">{%trans "Product" %}</th>
|
||||
<th class="tc bold" style="padding: 5px 0px 5px 0px !important;">{%trans "Quantity" %}</th>
|
||||
<th class="bold" style="text-align: right; padding: 5px 0px 5px 0px !important;">{%trans "Amount in " %} <span>CHF</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for record in bill.bill_records.all %}
|
||||
<tr class="ts" style="line-height:1.8em !important;">
|
||||
<td class="tl">
|
||||
<span>{{record.description}}</span>
|
||||
</td>
|
||||
<td class="tc">
|
||||
<span>{{record.quantity}}</span>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<span>{{record.subtotal}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="page">
|
||||
<div class="wf th">
|
||||
<p class="ts">
|
||||
<span class="tl">SubTotal</span>
|
||||
<span class="tr">{{bill.subtotal}}</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="ts">
|
||||
<span class="tl">{{bill.billing_address.get_country_display}} VAT {{vat_rate}}%</span>
|
||||
<span class="tr">{{tax_amount}}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="wf pc" style="background-color: #f5f5f5;padding-top:5px;padding-bottom:5px;">
|
||||
<p class="bold" style="padding-top:5px;">
|
||||
<span class="tl" style="font-size: 16px">{%trans "Total CHF" %}</span>
|
||||
<span class="tr" style="font-size: 16px">{{bill.sum}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -6,4 +6,9 @@ app_name = 'uncloud_pay'
|
|||
|
||||
urlpatterns = [
|
||||
path('cards/activate', CardActivateView.as_view(), name='card_activate'),
|
||||
path('billing/', PaymentsView.as_view(), name='billing'),
|
||||
path('billing/cards', CardsView.as_view(), name='cards'),
|
||||
path('billing/bills', BillsView.as_view(), name='bills'),
|
||||
path('order/success/', OrderSuccessView.as_view(), name='order_success'),
|
||||
path('order/invoice/<int:bill_id>/download/', InvoiceDownloadView.as_view(), name='invoice_download'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic.base import TemplateView, View
|
||||
from django.views.generic import DetailView
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.db import transaction
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import viewsets, mixins, permissions, status, views
|
||||
|
|
@ -13,8 +15,10 @@ from vat_validator import validate_vat, vies
|
|||
from vat_validator.countries import EU_COUNTRY_CODES
|
||||
from hardcopy import bytestring_to_pdf
|
||||
from django.core.files.temp import NamedTemporaryFile
|
||||
from django.http import FileResponse
|
||||
from django.views.generic.list import ListView
|
||||
from django.http import FileResponse, HttpResponseRedirect
|
||||
from django.template.loader import render_to_string
|
||||
from wkhtmltopdf.views import PDFTemplateResponse
|
||||
from copy import deepcopy
|
||||
|
||||
import json
|
||||
|
|
@ -88,6 +92,119 @@ class RegisterCard(TemplateView):
|
|||
context['stripe_pk'] = uncloud_stripe.public_api_key
|
||||
return context
|
||||
|
||||
class OrderSuccessView(DetailView):
|
||||
template_name = "uncloud_pay/order_success.html"
|
||||
context_object_name = "order"
|
||||
model = Order
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||||
def get(self, request, *args, **kwargs):
|
||||
if ('order' not in request.session or ('bill_id' not in request.session)):
|
||||
return HttpResponseRedirect(reverse('nextcloud:index'))
|
||||
context = {
|
||||
'order': self.request.session.get('order'),
|
||||
'bill_id': self.request.session['bill_id'],
|
||||
'balance': get_balance_for_user(self.request.user)
|
||||
}
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
class InvoiceDownloadView(View):
|
||||
template = 'uncloud_pay/invoice.html'
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {'base_url': f'{self.request.scheme}://{self.request.get_host()}'}
|
||||
return context
|
||||
|
||||
def get(self, request, bill_id):
|
||||
cmd_options = settings.REPORT_FORMAT
|
||||
context = self.get_context_data()
|
||||
bill = Bill.objects.get(owner=self.request.user, id=bill_id)
|
||||
if bill:
|
||||
context['bill'] = bill
|
||||
context['vat_rate'] = str(round(bill.vat_rate * 100, 2))
|
||||
context['tax_amount'] = round(bill.vat_rate * bill.subtotal, 2)
|
||||
return PDFTemplateResponse(request=request,
|
||||
template=self.template,
|
||||
filename = f"bill-{bill_id}.pdf",
|
||||
cmd_options= cmd_options,
|
||||
footer_template= 'uncloud_pay/includes/invoice_footer.html',
|
||||
context= context)
|
||||
|
||||
class PaymentsView(ListView):
|
||||
template_name = "uncloud_pay/payments.html"
|
||||
model = Payment
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(PaymentsView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'balance': get_balance_for_user(self.request.user),
|
||||
'type': self.request.GET.get('type')
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.GET.get('type'):
|
||||
return Payment.objects.filter(owner=self.request.user, type=self.request.GET.get('type')).order_by('-timestamp')
|
||||
return Payment.objects.filter(owner=self.request.user).order_by('-timestamp')
|
||||
|
||||
class CardsView(ListView):
|
||||
template_name = "uncloud_pay/cards.html"
|
||||
model = StripeCreditCard
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CardsView, self).get_context_data(**kwargs)
|
||||
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||||
setup_intent = uncloud_stripe.create_setup_intent(customer_id)
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'balance': get_balance_for_user(self.request.user),
|
||||
'client_secret': setup_intent.client_secret,
|
||||
'username': self.request.user.username,
|
||||
'stripe_pk':uncloud_stripe.public_api_key,
|
||||
'min_amount': settings.MIN_PER_TRANSACTION
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
uncloud_stripe.sync_cards_for_user(self.request.user)
|
||||
return StripeCreditCard.objects.filter(owner=self.request.user).order_by('-active')
|
||||
|
||||
class BillsView(ListView):
|
||||
template_name = "uncloud_pay/bills.html"
|
||||
model = Bill
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(BillsView, self).get_context_data(**kwargs)
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'balance': get_balance_for_user(self.request.user),
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
return Bill.objects.filter(owner=self.request.user).order_by('-creation_date')
|
||||
|
||||
class CreditCardViewSet(mixins.RetrieveModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue