diff options
author | luxagraf <sng@luxagraf.net> | 2015-03-16 22:31:32 -0400 |
---|---|---|
committer | luxagraf <sng@luxagraf.net> | 2015-03-16 22:31:32 -0400 |
commit | 50fe80969b9ddbfa795b11e303430d58a8e10a8c (patch) | |
tree | 05761e6f9347640c82efbcbd0b5658cac16cf834 | |
parent | 60e817f397f023f71ad48742cce09670ee7abd6d (diff) |
added expense tracking app
-rw-r--r-- | app/expenses/__init__.py | 0 | ||||
-rw-r--r-- | app/expenses/admin.py | 10 | ||||
-rw-r--r-- | app/expenses/models.py | 31 | ||||
-rw-r--r-- | app/expenses/urls.py | 7 | ||||
-rw-r--r-- | app/expenses/views.py | 12 | ||||
-rw-r--r-- | app/lib/templatetags/templatetags/expense_total.py | 25 | ||||
-rw-r--r-- | config/base_urls.py | 1 | ||||
-rw-r--r-- | design/sass/_projects.scss | 49 | ||||
-rw-r--r-- | design/templates/base.html | 1 | ||||
-rw-r--r-- | design/templates/details/expenses.html | 132 |
10 files changed, 268 insertions, 0 deletions
diff --git a/app/expenses/__init__.py b/app/expenses/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/expenses/__init__.py diff --git a/app/expenses/admin.py b/app/expenses/admin.py new file mode 100644 index 0000000..f014dca --- /dev/null +++ b/app/expenses/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from .models import Expense + + +class ExpenseAdmin(admin.ModelAdmin): + list_display = ('name', 'category', 'amount', 'date_month') + + +admin.site.register(Expense, ExpenseAdmin) diff --git a/app/expenses/models.py b/app/expenses/models.py new file mode 100644 index 0000000..a01b8d7 --- /dev/null +++ b/app/expenses/models.py @@ -0,0 +1,31 @@ +from django.db import models +from django.template.defaultfilters import slugify +import datetime + +CATS = ( + ('1', "Yellowstone"), + ('2', "Suburban"), + ('3', "Groceries"), + ('4', "Lodging"), + ('5', "Camping"), + ('6', "Restaurants"), + ('7', "Petrol"), + ('8', "Misc"), +) + +class Expense(models.Model): + name = models.CharField(max_length=200) + amount = models.DecimalField(max_digits=8, decimal_places=2) + date = models.DateTimeField(default=datetime.date.today()) + notes = models.TextField(null=True, blank=True) + category = models.CharField(max_length=2, choices=CATS, default=1) + + class Meta: + ordering = ('-date',) + + def __str__(self): + return self.name + + def date_month(self): + return self.date.strftime("%b %Y") + diff --git a/app/expenses/urls.py b/app/expenses/urls.py new file mode 100644 index 0000000..ae7cad6 --- /dev/null +++ b/app/expenses/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import * +from django.views.generic.base import RedirectView + + +urlpatterns = patterns('', + url(r'yellowstone-suburban-trip-costs/', 'expenses.views.detail'), +) diff --git a/app/expenses/views.py b/app/expenses/views.py new file mode 100644 index 0000000..7442178 --- /dev/null +++ b/app/expenses/views.py @@ -0,0 +1,12 @@ +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext + +from .models import Expense, CATS + +def detail(request): + context = { + 'object_list': Expense.objects.all(), + 'categories': CATS + } + return render_to_response('details/expenses.html', context, context_instance=RequestContext(request)) + diff --git a/app/lib/templatetags/templatetags/expense_total.py b/app/lib/templatetags/templatetags/expense_total.py new file mode 100644 index 0000000..8bf0953 --- /dev/null +++ b/app/lib/templatetags/templatetags/expense_total.py @@ -0,0 +1,25 @@ +from decimal import Decimal +from django import template +from django.utils.safestring import mark_safe +register = template.Library() + +@register.filter +def expense_total(values): + """ + converts spaces to hyphens. + """ + total = 0 + for items in values: + for item in items['list']: + total += Decimal(item.amount) + return mark_safe(total) + +@register.filter +def cat_total(values): + """ + converts spaces to hyphens. + """ + total = 0 + for val in values: + total += Decimal(val.amount) + return mark_safe(total) diff --git a/config/base_urls.py b/config/base_urls.py index 56612ff..c606171 100644 --- a/config/base_urls.py +++ b/config/base_urls.py @@ -52,6 +52,7 @@ urlpatterns += patterns('', #old: (r'(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'blog.views.entry_detail'), # locations (r'^locations/', include('locations.urls')), + (r'^expenses/', include('expenses.urls')), (r'^photos/', include('photos.urls')), (r'^field-notes/', include('notes.urls')), (r'^photo/', include('photos.detail_urls')), diff --git a/design/sass/_projects.scss b/design/sass/_projects.scss index fb3f530..e7cdf09 100644 --- a/design/sass/_projects.scss +++ b/design/sass/_projects.scss @@ -204,3 +204,52 @@ } } } + +//################# EXPENSE TRACKER TABLES ####################### + +.expense-wrapper { + @include constrain_narrow(); +} +.expense { + min-width: 90%; + margin-bottom: 3em; + caption { + text-align: left; + padding: 1em 0; + @include fontsize(18); + } + thead tr { + border-bottom: 1px solid darken(#eff7ff, 10); + padding: 0; + font-weight: bold; + @include smcaps; + font-family: sans-serif; + @include fontsize(12); + } + text-align: left; + td { + width: 80%; + } + tr td { + padding: .5em .25em; + color: lighten($body_font, 10); + } + .odd, .total { + background-color: #eff7ff; + } + .total { + border-top: 1px solid darken(#eff7ff, 10); + } + tfoot:after { + @include faded_line_after; + } +} + +.end:after { + @include faded_line_after; +} +.upfront tfoot:after { + margin: 0; + height: 0; + background: none; + } diff --git a/design/templates/base.html b/design/templates/base.html index 4fba739..4a8a182 100644 --- a/design/templates/base.html +++ b/design/templates/base.html @@ -60,6 +60,7 @@ <span class="h-card"><a class="p-name u-url" href="https://luxagraf.net/">Scott Gilbertson</a><data class="p-nickname" value="luxagraf"></data><data class="p-locality" value="Athens"></data><data class="p-region" value="Georgia"></data><data class="p-country-name" value="United States"></data></span>, except photos, which are licensed under the Creative Commons (<a href="http://creativecommons.org/licenses/by-sa/3.0/" title="read the Attribution-Share Alike 3.0 deed">details</a>). </p> </footer> + </div> {% block js %}{% endblock%} <script type="text/javascript">var _gaq=_gaq||[];_gaq.push(['_setAccount','UA-1186171-1']);_gaq.push(['_trackPageview']);(function(){var ga=document.createElement('script');ga.type='text/javascript';ga.async=true;ga.src=('https:'==document.location.protocol?'https://ssl':'http://www')+'.google-analytics.com/ga.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(ga,s)})();</script> </body> diff --git a/design/templates/details/expenses.html b/design/templates/details/expenses.html new file mode 100644 index 0000000..e2e1e9b --- /dev/null +++ b/design/templates/details/expenses.html @@ -0,0 +1,132 @@ +{% extends 'base.html' %} +{% load expense_total%} +{% load typogrify_tags %} + +{% block pagetitle %}Luxagraf: Expenses{% endblock %} + +{% block metadescription %}Think is costs a lot to drive around North America in a vintage Suburban and Yellowstone trailer? Well, judge for yourself, here's how much is costs us.{% endblock %} + +{% block primary %} + <main role="main"> + <article class="post--article"> + <header class="post--header"> + <h1 class="p-name entry-title post--title">Yellowstone/Suburban Trip Costs</h1> + <time class="dt-updated post--date" datetime="{%now 'c'%}">Last updated: {%now "F"%} <span>{%now "j, Y"%}</span></time> + </header> + <div id="article" class="post--body post--body--single expense-wrapper"> + <p>We're extremely fortunate to be able to do this. We afford it because we saved for a long time and I continue to work on the road. At the same time, people are usually surprised at how little it costs to live this way. When we were plainning this trip the people who posted their finances were invaluably helpful for calculating how much we needed to make this work so in the spirit of (hopefully, maybe) inspiring someone else to get out there, here is a rough breakdown of costs.</p> + <h2>Upfront Costs</h2> +<table class="expense upfront"> + <caption>Initial Investments</caption> + <thead> + <tr> + <td>Category</td> + <td>Amount</td> + </tr> + </thead> + <tbody> + + + <tr class="row odd"> + <td>1969 Yellowstone Trailer</td> + <td class="cat-value">$900.00 </td> + </tr> + + <tr class="row even"> + <td>1969 Chevy Suburban</td> + <td class="cat-value">??</td> + </tr> + + <tr class="row odd"> + <td>Yellowstone rebuild</td> + <td class="cat-value">$2020.00</td> + </tr> + + <tr class="row even"> + <td>Suburban rehab</td> + <td class="cat-value">$300.00 </td> + </tr> + + <tr class="row odd"> + <td>Inverter</td> + <td class="cat-value"></td> + </tr> + + <tr class="row even"> + <td>Solar setup</td> + <td class="cat-value">$81.74 </td> + </tr> + + <tr class="row odd"> + <td>DMV (taxes, title, etc)</td> + <td class="cat-value"></td> + </tr> + + <tr class="row even"> + <td>Insurance (1year)</td> + <td class="cat-value"></td> + </tr> + + <tr class="total"> + <td>Total</td> + <td>$???</td> + </tr> + </tbody> + <tfoot> + <tr> + <td colspan="2"><small>*Some expense</small></td> + </tr> + </tfoot> + </table> + <p>The intial investment is nothing to sneeze at and does not include the hundreds of hours of sweat equity invested as well, but then, I like to restore things and I take pride in doing it myself. Of course when I bought the trailer a few years ago we were $30K in consumer debt and it was arguably a very dumb purchase, but sometimes you have to gamble on a hunch. Or at least I do.</p> + <p>Practically speaking, do not buy an old trailer if you do not want to fix it up yourself. Hiring someone to do the work for you will be far more expensive than buying an already restored model. Also, restoration is not for the feint of heart. You can do it, even if you have no clue how (I didn't) but it does have its overwhelming/discouraging moments so be prepared for that. I wrote several articles about the process if you're interested. If that's not your bag you might be able to get a nicer rig for not much more money.</p> + <p class="end">Here's how much we've spent each month we've spent on the road since we left in June 2016.</p> + + <h2>Monthly Expenses</h2> +{% regroup object_list by date.month as expenses_by_month %}{% for expenses in expenses_by_month %} +<table class="expense"> + <caption>{{ expenses.list.0.date|date:"F Y" }}</caption> + <thead> + <tr> + <td>Category</td> + <td>Amount</td> + </tr> + </thead> + <tbody> +{% regroup expenses.list|dictsort:"category" by category as category_list %} + {% for cat in categories %} + <tr class="row {%cycle 'odd' 'even' %}"> + <td>{{cat.1}} </td> + <td class="cat-value">{% for clist in category_list %}{%if clist.grouper == cat.0%}${{clist.list|cat_total}} {%endif %}{%endfor%}</td> + </tr> +{% endfor %} + <tr class="total"> + <td>Total</td> + <td>${{category_list|expense_total}}</td> + </tr> + </tbody> + <tfoot> + <tr> + <td colspan="2">{% for clist in category_list %}{% for item in clist.list %}{% if item.notes %}<small>*{{item.notes|safe}}</small>{%endif %}{%endfor%}{%endfor%}</td> + </tr> + </tfoot> + </table> +{% endfor %} + </div> + </article> + </main> +{%endblock%} + +{% block js %} +<script type="text/javascript"> +window.onload = function() { + //delay loading + zchk = document.getElementsByClassName("cat-value"); + for(var i=0; i<zchk.length; i++) { + if (zchk[i].innerHTML == "") { + zchk[i].innerHTML = "–"; + }; + } +} +</script> +{%endblock%} |