def expense_list(request, bills_only=False):
    exp = revchron(Expense.objects.filter(user=request.user).select_related("category"))
    if bills_only:
        exp = exp.filter(is_bill=True)
        htmltitle = _("Bills")
        pid = "bill_list"
    else:
        htmltitle = _("Expenses")
        pid = "expense_list"

    paginator = Paginator(exp, settings.EXPENSES_PAGE_SIZE)
    page = request.GET.get("page", "1")
    expenses = paginator.get_page(page)
    if page == "1" and not bills_only:
        show_form = True
        categories = Category.user_objects(request)
    else:
        show_form = False
        categories = None
    return render(
        request,
        "expenses/expense_list.html",
        {
            "htmltitle": htmltitle,
            "pid": pid,
            "expenses": expenses,
            "bills_only": bills_only,
            "show_form": show_form,
            "categories": categories,
        },
    )
예제 #2
0
def category_show(request, slug):
    category = get_object_or_404(Category, slug=slug, user=request.user)
    paginator = Paginator(
        revchron(
            Expense.objects.filter(
                user=request.user,
                category=category).select_related("category")),
        settings.EXPENSES_PAGE_SIZE,
    )
    page = request.GET.get("page")
    expenses = paginator.get_page(page)
    return render(request, "expenses/category_show.html", {
        "expenses": expenses,
        "category": category,
        "pid": "category_show",
    })
예제 #3
0
def index(request):
    last_n_expenses = revchron(Expense.objects.filter(user=request.user).select_related("category"))[
        : settings.EXPENSES_INDEX_COUNT
    ]
    with connection.cursor() as cursor:
        cursor.execute(
            """
            SELECT date, SUM(amount)
            FROM expenses_expense
            WHERE user_id = %s
            GROUP BY date
            ORDER BY date DESC
            LIMIT 3""",
            [request.user.pk],
        )
        last_3_days = cursor.fetchall()
    last_3_days_sum = sum(r[1] for r in last_3_days)
    last_3_days = reversed(last_3_days)

    today = today_date()
    prev_month = today.month - 1
    prev_year = today.year
    while prev_month < 0:
        prev_month += 12
        prev_year -= 1

    current_months_total = Expense.objects.filter(
        user=request.user, date__year=today.year, date__month=today.month
    ).aggregate(Sum("amount"))["amount__sum"]
    previous_months_total = (
        Expense.objects.filter(user=request.user, date__year=prev_year, date__month=prev_month).aggregate(
            Sum("amount")
        )["amount__sum"]
        or 0
    )

    spending_per_category_qs = (
        Expense.objects.filter(user=request.user).values("category").annotate(sum=Sum("amount")).order_by("-sum")
    )
    categories = {cat.pk: cat for cat in Category.objects.filter(user=request.user)}
    spending_per_category = [(categories[i["category"]], i["sum"]) for i in spending_per_category_qs]

    pie_chart = pygal.Pie(
        disable_xml_declaration=True,
        margin=0,
        legend_at_bottom=True,
        print_values=True,
        print_labels=True,
        style=pygal.style.DefaultStyle(
            plot_background="white",
            background="white",
            font_family="var(--font-family-sans-serif)",
            label_font_size=20,
            value_font_size=20,
            value_label_font_size=20,
            tooltip_font_size=20,
            legend_font_size=20,
        ),
    )
    for c, s in spending_per_category:
        pie_chart.add(c.name, float(s), formatter=format_money)
    category_chart = mark_safe(pie_chart.render())

    return render(
        request,
        "expenses/index.html",
        {
            "htmltitle": _("Expenses Dashboard"),
            "pid": "expenses_index",
            "last_n_expenses": last_n_expenses,
            "EXPENSES_INDEX_COUNT": settings.EXPENSES_INDEX_COUNT,
            "last_3_days": last_3_days,
            "last_3_days_sum": last_3_days_sum,
            "current_months_total": current_months_total,
            "previous_months_total": previous_months_total,
            "spending_per_category": spending_per_category,
            "category_chart": category_chart,
        },
    )
예제 #4
0
def search(request):
    opt = {
        "q": "",
        "vendor": "",
        "search_for": "purchases",
        "date_spec": "any",
        "date_start": "",
        "date_end": ""
    }
    categories = Category.user_objects(request)
    if "q" in request.GET or "vendor" in request.GET:
        opt["has_query"] = True
        # Set search options (will be copied into template)
        dict_overwrite(opt, "q", request.GET)
        dict_overwrite(opt, "vendor", request.GET)
        dict_overwrite(opt, "search_for", request.GET, "for")
        dict_overwrite(opt, "date_spec", request.GET, "date-spec")
        dict_overwrite(opt, "date_start", request.GET, "date-start")
        dict_overwrite(opt, "date_end", request.GET, "date-end")

        includes = request.GET.getlist("include", [])
        opt["include_expenses"] = "expenses" in includes
        opt["include_bills"] = "bills" in includes

        if request.GET.get("category_all"):
            cat_pks = {cat.pk for cat in categories}
        else:
            cat_pks = {int(i) for i in request.GET.getlist("category", [])}
        categories_with_status = [(c, c.pk in cat_pks) for c in categories]

        # Do the search
        if opt["search_for"] == "expenses":
            items = Expense.objects.filter(
                user=request.user,
                category__in=cat_pks).select_related("category")
            if opt["q"]:
                items = items.filter(description_cache__icontains=opt["q"])
            if opt["vendor"]:
                items = items.filter(vendor__icontains=opt["vendor"])

            if opt["include_expenses"] and opt["include_bills"]:
                pass
            elif opt["include_expenses"]:
                items = items.filter(is_bill=False)
            elif opt["include_bills"]:
                items = items.filter(is_bill=True)

            if opt["date_start"] and not opt["date_end"]:
                items = items.filter(date__gte=opt["date_start"])
            elif opt["date_start"] and opt["date_end"]:
                items = items.filter(date__gte=opt["date_start"],
                                     date__lte=opt["date_end"])

            items = revchron(items)
        elif opt["search_for"] == "billitems":
            items = BillItem.objects.filter(
                user=request.user,
                bill__category__in=cat_pks).select_related("bill")
            if opt["q"]:
                items = items.filter(product__icontains=opt["q"])
            if opt["vendor"]:
                items = items.filter(bill__vendor__icontains=opt["vendor"])

            if opt["date_start"] and not opt["date_end"]:
                items = items.filter(bill__date__gte=opt["date_start"])
            elif opt["date_start"] and opt["date_end"]:
                items = items.filter(bill__date__gte=opt["date_start"],
                                     bill__date__lte=opt["date_end"])

            items = items.order_by("-date_added")
        elif opt["search_for"] == "purchases":
            cat_pks = {int(i) for i in request.GET.getlist("category", [])}

            if opt["date_start"] and not opt["date_end"]:
                date_clause = "AND d.date >= %s"
                date_args = [opt["date_start"]]
            elif opt["date_start"] and opt["date_end"]:
                date_clause = "AND d.date BETWEEN %s AND %s"
                date_args = [opt["date_start"], opt["date_end"]]
            else:
                date_clause = ""
                date_args = []

            ilike_word = "LIKE" if connection.settings_dict[
                "ENGINE"] == "django.db.backends.sqlite3" else "ILIKE"

            query_clause = ""
            query_args = []
            if opt["q"]:
                query_clause += " AND d.product " + ilike_word + " %s"
                query_args.append("%" + opt["q"] + "%")
            if opt["vendor"]:
                query_clause += " AND d.vendor " + ilike_word + " %s"
                query_args.append("%" + opt["vendor"] + "%")

            items = RawQueryWithSlicing(
                """
                SELECT {{selected_fields}} FROM (
                    SELECT date, vendor, description AS product, amount AS unit_price, category_id, date_added
                    FROM expenses_expense WHERE is_bill = false AND user_id = %s
                UNION
                    SELECT date, vendor, product, unit_price, category_id, expenses_billitem.date_added
                    FROM expenses_billitem
                    LEFT JOIN expenses_expense ON expenses_billitem.bill_id = expenses_expense.id
                    WHERE expenses_billitem.user_id = %s
                ) AS d
                WHERE d.category_id in ({cat_pks}) {date_clause}{query_clause}
                {{order_clause}} {{limit_clause}};""".format(
                    cat_pks=", ".join(str(i) for i in cat_pks),
                    date_clause=date_clause,
                    query_clause=query_clause),
                [request.user.pk, request.user.pk] + date_args + query_args,
                "d.date, d.vendor, d.product, d.unit_price",
                "ORDER BY d.date DESC, d.date_added DESC",
            )
        else:
            raise Exception("Unknown search type")
    else:
        opt["include_expenses"] = True
        opt["include_bills"] = True
        opt["has_query"] = False
        categories_with_status = [(c, True) for c in categories]
        items = None

    context = {
        "htmltitle": _("Search"),
        "pid": "search",
        "categories_with_status": categories_with_status
    }
    context.update(opt)
    if items is not None:
        paginator = Paginator(items, settings.EXPENSES_PAGE_SIZE)
        page = request.GET.get("page", "1")
        context["items"] = paginator.get_page(page)
    return render(request, "expenses/search.html", context)