fix a few things in backoffice chain/credebtor views and templates, and update bootstrap_devsite script to create chains, credebtors, expenses and revenues

This commit is contained in:
Thomas Steen Rasmussen 2020-10-16 17:22:41 +02:00
parent 02f0d77c21
commit 833fc85e46
7 changed files with 123 additions and 19 deletions

View file

@ -7,7 +7,7 @@ Details for Chain {{ chain.name }} | {{ block.super }}
{% block content %}
<h2>Details for Chain {{ chain.name }}</h2>
<a href="{% url "backoffice:chain_list" camp_slug=camp.slug %}">Back to Chain list</a>
<a class="btn btn-default" href="{% url "backoffice:chain_list" camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Back to Chain list</a>
<h3>{{ chain.credebtors.count }} Credebtors for Chain {{ chain.name }}</h3>
<table class="table table-hover">
@ -26,7 +26,7 @@ Details for Chain {{ chain.name }} | {{ block.super }}
<tbody>
{% for credebtor in chain.credebtors.all %}
<tr>
<td>{{ credebtor.name }}</td>
<td><a class="btn btn-primary" href="{% url 'backoffice:credebtor_detail' camp_slug=camp.slug chain_slug=chain.slug credebtor_slug=credebtor.slug %}">{{ credebtor.name }}</a></td>
<td><address>{{ credebtor.address }}</address></td>
<td>{{ credebtor.notes|default:"N/A" }}</td>
<td class="text-center"><span class="badge">{{ credebtor.expenses.count }}</span></td>
@ -39,10 +39,10 @@ Details for Chain {{ chain.name }} | {{ block.super }}
</tbody>
</table>
<h3>{{ chain.expenses.count }} Expenses for Chain {{ chain.name }}</h3>
<h3>{{ chain.expenses_count }} Expenses for Chain {{ chain.name }}</h3>
{% include 'includes/expense_list_panel.html' with expense_list=chain.expenses.all %}
<h3>{{ chain.revenues.count }} Revenues for Chain {{ chain.name }}</h3>
<h3>{{ chain.revenues_count }} Revenues for Chain {{ chain.name }}</h3>
{% include 'includes/revenue_list_panel.html' with revenue_list=chain.revenues.all %}
{% endblock content %}

View file

@ -8,7 +8,7 @@ Select Chain | {{ block.super }}
{% block content %}
<h2>Chains</h2>
<p><a href="{% url "backoffice:index" camp_slug=camp.slug %}">Back to Backoffice Index</a></p>
<p><a class="btn btn-default" href="{% url "backoffice:index" camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Back to Backoffice Index</a></p>
{% if chain_list %}
<table class="table table-hover datatable">
@ -30,9 +30,9 @@ Select Chain | {{ block.super }}
<td>{{ chain.name }}</td>
<td>{{ chain.notes|default:"N/A" }}</td>
<td class="text-center"><span class="badge">{{ chain.credebtors.count }}</span></td>
<td class="text-center"><span class="badge">{{ chain.expenses.count }}</span></td>
<td class="text-center"><span class="badge">{{ chain.expenses_count }}</span></td>
<td class="text-center">{{ chain.expenses_total|default:"0" }} DKK</td>
<td class="text-center"><span class="badge">{{ chain.revenues.count }}</span></td>
<td class="text-center"><span class="badge">{{ chain.revenues_count }}</span></td>
<td class="text-center">{{ chain.revenues_total|default:"0" }} DKK</td>
<td>
<a class="btn btn-primary" href="{% url 'backoffice:chain_detail' camp_slug=camp.slug chain_slug=chain.slug %}"><i class="fas fa-search"></i> Details</a>

View file

@ -7,7 +7,7 @@ Details for Credebtor {{ credebtor.name }} (Chain {{ credebtor.chain.name }}) |
{% block content %}
<h2>Details for Credebtor {{ credebtor.name }} (Chain {{ credebtor.chain.name }})</h2>
<a href="{% url "backoffice:chain_detail" camp_slug=camp.slug chain_slug=credebtor.chain.slug %}">Back to Credebtor list</a>
<a class="btn btn-default" href="{% url "backoffice:chain_detail" camp_slug=camp.slug chain_slug=credebtor.chain.slug %}"><i class="fas fa-undo"></i> Back to Credebtor list</a>
<h3>{{ credebtor.expenses.count }} Expenses for Credebtor {{ credebtor.name }}</h3>
{% include 'includes/expense_list_panel.html' with expense_list=credebtor.expenses.all %}

View file

@ -16,8 +16,8 @@ class ChainAdmin(admin.ModelAdmin):
@admin.register(Credebtor)
class CredebtorAdmin(admin.ModelAdmin):
list_filter = ["chain", "name"]
list_display = ["chain", "name", "notes"]
search_fields = ["chain", "name", "notes"]
list_display = ["name", "chain", "address", "notes"]
search_fields = ["chain", "name", "address", "notes"]
###############################

View file

@ -20,12 +20,17 @@ from .email import (
class ChainManager(models.Manager):
"""
ChainManager adds 'expenses_total' and 'revenues_total' to the Chain qs
Also adds 'expenses_count' and 'revenues_count' and prefetches all expenses
and revenues for the credebtors.
"""
def get_queryset(self):
qs = super().get_queryset()
qs = qs.prefetch_related("credebtors__expenses", "credebtors__revenues")
qs = qs.annotate(expenses_total=models.Sum("credebtors__expenses__amount"))
qs = qs.annotate(expenses_count=models.Count("credebtors__expenses", distinct=True))
qs = qs.annotate(revenues_total=models.Sum("credebtors__revenues__amount"))
qs = qs.annotate(revenues_count=models.Count("credebtors__revenues", distinct=True))
return qs
@ -81,11 +86,13 @@ class Chain(CreatedUpdatedModel, UUIDModel):
class CredebtorManager(models.Manager):
"""
CredebtorManager adds 'expenses_total' and 'revenues_total' to the Credebtor qs
CredebtorManager adds 'expenses_total' and 'revenues_total' to the Credebtor qs,
and prefetches expenses and revenues for the credebtor(s).
"""
def get_queryset(self):
qs = super().get_queryset()
qs = qs.prefetch_related("expenses", "revenues")
qs = qs.annotate(expenses_total=models.Sum("expenses__amount"))
qs = qs.annotate(revenues_total=models.Sum("revenues__amount"))
return qs

View file

@ -1,5 +1,5 @@
{% if expense_list %}
<table class="table table-hover">
<table class="table table-hover datatable">
<thead>
<tr>
<th>Invoice Date</th>

View file

@ -12,6 +12,7 @@ from django.contrib.auth.models import User
from django.contrib.gis.geos import Point
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand
from django.core.management import call_command
from django.db.models.signals import post_save
from django.utils import timezone
from django.utils.crypto import get_random_string
@ -52,13 +53,65 @@ from tickets.models import TicketType
from tokens.models import Token, TokenFind
from utils.slugs import unique_slugify
from villages.models import Village
from economy.models import Chain, Credebtor, Expense, Revenue
fake = Faker()
tz = pytz.timezone("Europe/Copenhagen")
logger = logging.getLogger("bornhack.%s" % __name__)
@factory.django.mute_signals(post_save)
class ChainFactory(factory.django.DjangoModelFactory):
class Meta:
model = Chain
name = factory.Faker("company")
slug = factory.LazyAttribute(lambda f: unique_slugify(f.name, Chain.objects.all().values_list("slug", flat=True)))
class CredebtorFactory(factory.django.DjangoModelFactory):
class Meta:
model = Credebtor
chain = factory.SubFactory(ChainFactory)
name = factory.Faker("company")
slug = factory.LazyAttribute(lambda f: unique_slugify(f.name, Credebtor.objects.all().values_list("slug", flat=True)))
address = factory.Faker("address", locale="dk_DK")
notes = factory.Faker("text")
class ExpenseFactory(factory.django.DjangoModelFactory):
class Meta:
model = Expense
camp = factory.Faker("random_element", elements=Camp.objects.all())
creditor = factory.Faker("random_element", elements=Credebtor.objects.all())
user = factory.Faker("random_element", elements=User.objects.all())
amount = factory.Faker("random_int", min=20, max=20000)
description = factory.Faker("text")
paid_by_bornhack = factory.Faker("random_element", elements=[True, True, False])
invoice = factory.django.ImageField(color=random.choice(['#ff0000', '#00ff00', '#0000ff']))
invoice_date = factory.Faker("date")
responsible_team = factory.Faker("random_element", elements=Team.objects.all())
approved = factory.Faker("random_element", elements=[True, True, False])
notes = factory.Faker("text")
class RevenueFactory(factory.django.DjangoModelFactory):
class Meta:
model = Revenue
camp = factory.Faker("random_element", elements=Camp.objects.all())
debtor = factory.Faker("random_element", elements=Credebtor.objects.all())
user = factory.Faker("random_element", elements=User.objects.all())
amount = factory.Faker("random_int", min=20, max=20000)
description = factory.Faker("text")
invoice = factory.django.ImageField(color=random.choice(['#ff0000', '#00ff00', '#0000ff']))
invoice_date = factory.Faker("date")
responsible_team = factory.Faker("random_element", elements=Team.objects.all())
approved = factory.Faker("random_element", elements=[True, True, False])
notes = factory.Faker("text")
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = Profile
@ -159,8 +212,9 @@ class Command(BaseCommand):
dict(year=2017, tagline="Make Tradition", colour="#750787", read_only=True),
dict(year=2018, tagline="scale it", colour="#008026", read_only=True),
dict(year=2019, tagline="a new /home", colour="#ffed00", read_only=True),
dict(year=2020, tagline="Going Viral", colour="#ff8c00", read_only=False),
dict(year=2020, tagline="Make Clean", colour="#ff8c00", read_only=False),
dict(year=2021, tagline="Undecided", colour="#e40303", read_only=False),
dict(year=2022, tagline="Undecided", colour="#004dff", read_only=False),
]
camp_instances = []
@ -508,6 +562,21 @@ class Command(BaseCommand):
name="Recording", defaults={"icon": "fas fa-film"}
)
def create_credebtors(self):
self.output("Creating Chains and Credebtors...")
try:
CredebtorFactory.create_batch(50)
except ValidationError:
self.outout("Name conflict, retrying...")
CredebtorFactory.create_batch(50)
for _ in range(20):
# add 20 more credebtors to random existing chains
try:
CredebtorFactory.create(chain=Chain.objects.order_by("?").first())
except ValidationError:
self.outout("Name conflict, skipping...")
continue
def create_product_categories(self):
categories = {}
self.output("Creating productcategories...")
@ -686,7 +755,7 @@ class Command(BaseCommand):
orders = {}
self.output("Creating orders...")
orders[0] = Order.objects.create(
user=users[1], payment_method="cash", open=None, paid=True
user=users[1], payment_method="in_person", open=None, paid=True
)
orders[0].orderproductrelation_set.create(
product=camp_products["ticket1"], quantity=1
@ -697,7 +766,7 @@ class Command(BaseCommand):
orders[0].mark_as_paid(request=None)
orders[1] = Order.objects.create(
user=users[2], payment_method="cash", open=None
user=users[2], payment_method="in_person", open=None
)
orders[1].orderproductrelation_set.create(
product=camp_products["ticket1"], quantity=1
@ -708,7 +777,7 @@ class Command(BaseCommand):
orders[1].mark_as_paid(request=None)
orders[2] = Order.objects.create(
user=users[3], payment_method="cash", open=None
user=users[3], payment_method="in_person", open=None
)
orders[2].orderproductrelation_set.create(
product=camp_products["ticket2"], quantity=1
@ -722,7 +791,7 @@ class Command(BaseCommand):
orders[2].mark_as_paid(request=None)
orders[3] = Order.objects.create(
user=users[4], payment_method="cash", open=None
user=users[4], payment_method="in_person", open=None
)
orders[3].orderproductrelation_set.create(
product=global_products["product0"], quantity=1
@ -1189,6 +1258,13 @@ class Command(BaseCommand):
mailing_list="content@example.com",
permission_set="contentteam_permission",
)
teams["economy"] = Team.objects.create(
name="Economy",
description="The Economy Team handles the money and accounts.",
camp=camp,
mailing_list="economy@example.com",
permission_set="economyteam_permission",
)
return teams
@ -1600,6 +1676,14 @@ class Command(BaseCommand):
for i in range(0, 6):
TokenFind.objects.create(token=tokens[i], user=users[1])
def create_camp_expenses(self, camp):
self.output(f"Creating expenses for {camp}...")
ExpenseFactory.create_batch(200, camp=camp)
def create_camp_revenues(self, camp):
self.output(f"Creating revenues for {camp}...")
RevenueFactory.create_batch(20, camp=camp)
def output(self, message):
self.stdout.write(
"%s: %s" % (timezone.now().strftime("%Y-%m-%d %H:%M:%S"), message)
@ -1610,6 +1694,13 @@ class Command(BaseCommand):
self.output(
self.style.SUCCESS("----------[ Running bootstrap-devsite ]----------")
)
self.output(
self.style.SUCCESS(
"----------[ Deleting all data from database ]----------"
)
)
call_command("flush", "--noinput")
self.output(self.style.SUCCESS("----------[ Global stuff ]----------"))
camps = self.create_camps()
@ -1628,6 +1719,8 @@ class Command(BaseCommand):
quickfeedback_options = self.create_quickfeedback_options()
self.create_credebtors()
for (camp, read_only) in camps:
year = camp.camp.lower.year
@ -1635,7 +1728,7 @@ class Command(BaseCommand):
self.style.SUCCESS("----------[ Bornhack {} ]----------".format(year))
)
if year < 2021:
if year < 2022:
ticket_types = self.create_camp_ticket_types(camp)
camp_products = self.create_camp_products(
@ -1716,6 +1809,10 @@ class Command(BaseCommand):
tokens = self.create_camp_tokens(camp)
self.create_camp_token_finds(camp, tokens, users)
self.create_camp_expenses(camp)
self.create_camp_revenues(camp)
else:
self.output("Not creating anything for this year yet")