Add FuelPurchase model, List, Create views and templates
This commit is contained in:
parent
64576a7f09
commit
fa19b593d8
10 changed files with 242 additions and 4 deletions
|
@ -55,7 +55,9 @@ ROOT_URLCONF = 'config.urls'
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [
|
||||||
|
'templates',
|
||||||
|
],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
from .models import FuelPurchase
|
||||||
|
|
||||||
|
admin.site.register(FuelPurchase)
|
||||||
|
|
31
main/forms.py
Normal file
31
main/forms.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from .models import FuelPurchase
|
||||||
|
|
||||||
|
|
||||||
|
class FuelPurchaseForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = FuelPurchase
|
||||||
|
fields = ['purchase_date', 'total_cost', 'price_per_litre', 'amount_litres']
|
||||||
|
widgets = {
|
||||||
|
'purchase_date': forms.DateInput(attrs={
|
||||||
|
'type': 'date',
|
||||||
|
'class': 'uk-input'
|
||||||
|
}),
|
||||||
|
'total_cost': forms.NumberInput(attrs={
|
||||||
|
'step': '0.01',
|
||||||
|
'class': 'uk-input',
|
||||||
|
'id': 'id_total_cost'
|
||||||
|
}),
|
||||||
|
'price_per_litre': forms.NumberInput(attrs={
|
||||||
|
'step': '0.001',
|
||||||
|
'class': 'uk-input',
|
||||||
|
'id': 'id_price_per_litre'
|
||||||
|
}),
|
||||||
|
'amount_litres': forms.NumberInput(attrs={
|
||||||
|
'step': '0.01',
|
||||||
|
'class': 'uk-input',
|
||||||
|
'id': 'id_amount_litres',
|
||||||
|
'readonly': True
|
||||||
|
}),
|
||||||
|
}
|
29
main/migrations/0001_initial.py
Normal file
29
main/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 5.2.5 on 2025-08-09 17:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FuelPurchase',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('purchase_date', models.DateField(verbose_name='Purchase Date')),
|
||||||
|
('total_cost', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Total Cost (€)')),
|
||||||
|
('price_per_litre', models.DecimalField(decimal_places=3, max_digits=6, verbose_name='Price per Litre (€)')),
|
||||||
|
('amount_litres', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='Amount (litres)')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Fuel Purchase',
|
||||||
|
'verbose_name_plural': 'Fuel Purchases',
|
||||||
|
'constraints': [models.UniqueConstraint(fields=('purchase_date', 'total_cost', 'price_per_litre', 'amount_litres'), name='unique_fuel_purchase'), models.CheckConstraint(condition=models.Q(('total_cost__gt', 0), ('total_cost__lt', 100)), name='total_cost_range'), models.CheckConstraint(condition=models.Q(('price_per_litre__gt', 0), ('price_per_litre__lt', 5)), name='price_per_litre_range'), models.CheckConstraint(condition=models.Q(('amount_litres__gt', 0), ('amount_litres__lt', 100)), name='amount_litres_range')],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,3 +1,45 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import F, Q
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
class FuelPurchase(models.Model):
|
||||||
|
purchase_date = models.DateField(null=False, blank=False, verbose_name="Purchase Date")
|
||||||
|
total_cost = models.DecimalField(null=False, blank=False, max_digits=10, decimal_places=2, verbose_name="Total Cost (€)")
|
||||||
|
price_per_litre = models.DecimalField(null=False, blank=False, max_digits=6, decimal_places=3, verbose_name="Price per Litre (€)")
|
||||||
|
amount_litres = models.DecimalField(null=False, blank=False, max_digits=7, decimal_places=2, verbose_name="Amount (litres)")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=[
|
||||||
|
'purchase_date',
|
||||||
|
'total_cost',
|
||||||
|
'price_per_litre',
|
||||||
|
'amount_litres',
|
||||||
|
],
|
||||||
|
name='unique_fuel_purchase'
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
check=Q(total_cost__gt=0) & Q(total_cost__lt=100),
|
||||||
|
name="total_cost_range"
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
check=Q(price_per_litre__gt=0) & Q(price_per_litre__lt=5),
|
||||||
|
name="price_per_litre_range"
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
check=Q(amount_litres__gt=0) & Q(amount_litres__lt=100),
|
||||||
|
name="amount_litres_range"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
verbose_name = "Fuel Purchase"
|
||||||
|
verbose_name_plural = "Fuel Purchases"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.amount_litres and self.price_per_litre:
|
||||||
|
self.amount_litres = self.total_cost / self.price_per_litre
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
date_str = self.purchase_date.strftime("%d.%m.%Y")
|
||||||
|
return f"{date_str} : {self.total_cost} € - {self.price_per_litre} €/L"
|
||||||
|
|
51
main/templates/main/fuelpurchase_add.html
Normal file
51
main/templates/main/fuelpurchase_add.html
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Add Fuel Purchase{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2 class="uk-heading-line"><span>Add Fuel Purchase</span></h2>
|
||||||
|
|
||||||
|
<form method="post" class="uk-form-stacked">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<p class="uk-text-meta">{{ field.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<p class="uk-text-danger">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<button type="submit" class="uk-button uk-button-primary">Save</button>
|
||||||
|
<a href="{% url 'fuelpurchase_list' %}" class="uk-button uk-button-default">Cancel</a>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const totalCostInput = document.getElementById("id_total_cost");
|
||||||
|
const pricePerLitreInput = document.getElementById("id_price_per_litre");
|
||||||
|
const amountLitresInput = document.getElementById("id_amount_litres");
|
||||||
|
|
||||||
|
function updateAmountLitres() {
|
||||||
|
const total = parseFloat(totalCostInput.value);
|
||||||
|
const price = parseFloat(pricePerLitreInput.value);
|
||||||
|
if (!isNaN(total) && !isNaN(price) && price > 0) {
|
||||||
|
amountLitresInput.value = (total / price).toFixed(2);
|
||||||
|
} else {
|
||||||
|
amountLitresInput.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCostInput.addEventListener("input", updateAmountLitres);
|
||||||
|
pricePerLitreInput.addEventListener("input", updateAmountLitres);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
35
main/templates/main/fuelpurchase_list.html
Normal file
35
main/templates/main/fuelpurchase_list.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Fuel Purchases{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="uk-flex uk-flex-between uk-flex-middle uk-margin-bottom">
|
||||||
|
<h2 class="uk-heading-line"><span>Fuel Purchases</span></h2>
|
||||||
|
<a href="{% url 'fuelpurchase_add' %}" class="uk-button uk-button-primary">Add Purchase</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if purchases %}
|
||||||
|
<table class="uk-table uk-table-divider uk-table-striped uk-table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Total Cost (€)</th>
|
||||||
|
<th>Price/Litre (€)</th>
|
||||||
|
<th>Amount (litres)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for purchase in purchases %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ purchase.purchase_date|date:"d.m.Y" }}</td>
|
||||||
|
<td>{{ purchase.total_cost }}</td>
|
||||||
|
<td>{{ purchase.price_per_litre }}</td>
|
||||||
|
<td>{{ purchase.amount_litres }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p class="uk-text-meta">No purchases recorded yet.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -3,5 +3,6 @@ from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="index"),
|
path("", views.FuelPurchaseListView.as_view(), name="fuelpurchase_list"),
|
||||||
|
path("add/", views.FuelPurchaseCreateView.as_view(), name="fuelpurchase_add"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,25 @@
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.views.generic import CreateView, ListView
|
||||||
|
|
||||||
|
from .forms import FuelPurchaseForm
|
||||||
|
from .models import FuelPurchase
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
return HttpResponse("Hello, world.")
|
return HttpResponse("Hello, world.")
|
||||||
|
|
||||||
|
|
||||||
|
class FuelPurchaseListView(ListView):
|
||||||
|
model = FuelPurchase
|
||||||
|
template_name = "main/fuelpurchase_list.html"
|
||||||
|
context_object_name = "purchases"
|
||||||
|
ordering = ['-purchase_date']
|
||||||
|
|
||||||
|
|
||||||
|
class FuelPurchaseCreateView(CreateView):
|
||||||
|
model = FuelPurchase
|
||||||
|
form_class = FuelPurchaseForm
|
||||||
|
template_name = "main/fuelpurchase_add.html"
|
||||||
|
success_url = "/"
|
||||||
|
|
26
templates/base.html
Normal file
26
templates/base.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}Fuel Tracker{% endblock %}</title>
|
||||||
|
|
||||||
|
<!-- UIKit CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.17.11/dist/css/uikit.min.css" />
|
||||||
|
|
||||||
|
<!-- Main CSS -->
|
||||||
|
<link rel="stylesheet" href="{% static 'css/style.css' %}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<div class="uk-container uk-margin-top">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- UIKit JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.17.11/dist/js/uikit.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.17.11/dist/js/uikit-icons.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue