initial commit

This commit is contained in:
tsb 2024-12-18 11:28:35 -10:00
commit f677973feb
45 changed files with 922 additions and 0 deletions

0
baby_tracker/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
baby_tracker/asgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
ASGI config for baby_tracker project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'baby_tracker.settings')
application = get_asgi_application()

128
baby_tracker/settings.py Normal file
View File

@ -0,0 +1,128 @@
"""
Django settings for baby_tracker project.
Generated by 'django-admin startproject' using Django 5.1.4.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-q_$lvms3r!+meum_0o&b^#vkf75+sy!kax9v^pyr@fipj6&2$a'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'main',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'baby_tracker.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'baby_tracker.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Pacific/Honolulu'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / "static"]

23
baby_tracker/urls.py Normal file
View File

@ -0,0 +1,23 @@
"""
URL configuration for baby_tracker project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('main.urls')),
]

16
baby_tracker/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for baby_tracker project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'baby_tracker.settings')
application = get_wsgi_application()

BIN
db.sqlite3 Normal file

Binary file not shown.

0
main/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

23
main/admin.py Normal file
View File

@ -0,0 +1,23 @@
# admin.py
from django.contrib import admin
from .models import Feeding, Potty
class PottyAdmin(admin.ModelAdmin):
# Show timestamp in the list view
list_display = ('timestamp', 'consistency', 'notes') # Add timestamp here
# Allow timestamp to be editable in the form view
fields = ('consistency', 'notes') # Add timestamp here to the edit form
admin.site.register(Potty, PottyAdmin)
class FeedingAdmin(admin.ModelAdmin):
# Show timestamp in the list view
list_display = ('timestamp', 'feeding_type', 'amount', 'duration' , 'notes') # Add timestamp here
# Allow timestamp to be editable in the form view
fields = ('feeding_type', 'amount', 'duration' , 'notes') # Add timestamp here to the edit form
admin.site.register(Feeding, FeedingAdmin)

6
main/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MainConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'main'

37
main/forms.py Normal file
View File

@ -0,0 +1,37 @@
from django import forms
from .models import Feeding, Potty
class FeedingForm(forms.ModelForm):
class Meta:
model = Feeding
fields = ['feeding_type', 'amount', 'duration', 'notes']
widgets = {
'feeding_type': forms.Select(attrs={'class': 'form-control'}),
'amount': forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Enter amount (if bottle) in ounces'
}),
'duration': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter duration (if breast) in minutes'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'placeholder': 'Additional notes (optional)',
'rows': 3
}),
}
class PottyForm(forms.ModelForm):
class Meta:
model = Potty
fields = ['consistency', 'notes']
widgets = {
'consistency': forms.Select(attrs={'class': 'form-control'}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'placeholder': 'Additional notes (optional)',
'rows': 3
})
}

View File

@ -0,0 +1,34 @@
# Generated by Django 5.1.4 on 2024-12-07 10:40
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Feeding',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(auto_now_add=True)),
('feeding_type', models.CharField(choices=[('bottle', 'Bottle'), ('breast', 'Breast')], max_length=10)),
('amount', models.FloatField(blank=True, null=True)),
('duration', models.FloatField(blank=True, null=True)),
('notes', models.TextField(blank=True)),
],
),
migrations.CreateModel(
name='Potty',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(auto_now_add=True)),
('consistency', models.CharField(choices=[('solid', 'Solid'), ('soft', 'Soft'), ('watery', 'Watery'), ('pee', 'Pee'), ('other', 'Other')], max_length=50)),
('notes', models.TextField(blank=True)),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2024-12-07 18:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='feeding',
name='amount',
field=models.IntegerField(blank=True, null=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.1.4 on 2024-12-07 18:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0002_alter_feeding_amount'),
]
operations = [
migrations.AlterField(
model_name='feeding',
name='amount',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='feeding',
name='duration',
field=models.IntegerField(blank=True, null=True),
),
]

View File

Binary file not shown.

34
main/models.py Normal file
View File

@ -0,0 +1,34 @@
from django.db import models
# Create your models here.
from django.db import models
class Feeding(models.Model):
FEEDING_TYPES = [
('bottle', 'Bottle'),
('breast', 'Breast'),
]
timestamp = models.DateTimeField(auto_now_add=True)
feeding_type = models.CharField(max_length=10, choices=FEEDING_TYPES)
amount = models.FloatField(null=True, blank=True) # Store ounces for bottle feed
duration = models.IntegerField(null=True, blank=True) # Store duration in minutes for breastfeed
notes = models.TextField(blank=True)
def __str__(self):
return f"{self.get_feeding_type_display()} - {self.timestamp}"
class Potty(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
consistency = models.CharField(max_length=50, choices=[
('solid', 'Solid'),
('soft', 'Soft'),
('watery', 'Watery'),
('pee', 'Pee'),
('other', 'Other'),
])
notes = models.TextField(blank=True)
def __str__(self):
return f"{self.get_potty_type_display()} - {self.timestamp}"

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Baby Tracker</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<!-- HTMX -->
<script src="https://unpkg.com/htmx.org"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body class="p-3 bg-secondary text-white">
<div class="container">
<header>
<h1 class="text-center">Caelum's Poop and Feed Tracker</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
</body>
</html>

View File

@ -0,0 +1,5 @@
{% extends 'main/base.html' %}
{% block content %}
{% include 'main/partials/feed_section.html' %}
{% include 'main/partials/potty_section.html' %}
{% endblock %}

View File

@ -0,0 +1,40 @@
<form method="post" action="{% url 'add_feed' %}" hx-post="{% url 'add_feed' %}" hx-target="#feed-section" hx-swap="outerHTML">
{% csrf_token %}
<div class="mb-3">
{{ form.feeding_type.label_tag }}
{{ form.feeding_type }}
</div>
<div id="bottle-fields" class="mb-3" style="display: none;">
{{ form.amount.label_tag }}
{{ form.amount }}
</div>
<div id="breast-fields" class="mb-3" style="display: none;">
{{ form.duration.label_tag }}
{{ form.duration }}
</div>
<div id="notes-fields" class="mb-3" >
{{ form.notes.label_tag }}
{{ form.notes }}
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
function toggleFields() {
if (document.getElementById("id_feeding_type").value === "bottle") {
document.getElementById("bottle-fields").style.display = "block";
document.getElementById("breast-fields").style.display = "none";
} else if (document.getElementById("id_feeding_type").value === "breast") {
document.getElementById("bottle-fields").style.display = "none";
document.getElementById("breast-fields").style.display = "block";
} else {
document.getElementById("bottle-fields").style.display = "none";
document.getElementById("breast-fields").style.display = "none";
}
}
document.getElementById("id_feeding_type").addEventListener("change", toggleFields);
toggleFields(); // Initial toggle
</script>

View File

@ -0,0 +1,13 @@
<form method="post" action="{% url 'add_potty' %}" hx-post="{% url 'add_potty' %}" hx-target="#potty-section" hx-swap="outerHTML">
{% csrf_token %}
<div class="mb-3">
{{form.consistency.label_tag}}
{{form.consistency}}
</div>
<div class="mb-3">
{{form.notes.label_tag}}
{{form.notes}}
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>

View File

@ -0,0 +1,37 @@
<tr id="feeding-{{ feeding.id }}">
<td>
<input type="datetime-local" name="timestamp" value="{{ feeding.timestamp|date:'Y-m-d\TH:i' }}" class="form-control">
</td>
<td>
{% if feeding.feeding_type == 'bottle' %}
<input type="number" name="amount_or_duration" value="{{ feeding.amount }}"
class="form-control" placeholder="Enter amount (in oz)">
{% elif feeding.feeding_type == 'breast' %}
<input type="number" name="amount_or_duration" value="{{ feeding.duration }}"
class="form-control" placeholder="Enter duration (in min)">
{% else %}
<input type="number" name="amount_or_duration" class="form-control"
placeholder="Enter value">
{% endif %}
</td>
<td>
<input type="text" name="notes" value="{{ feeding.notes }}" class="form-control">
</td>
<td>
<button class="btn btn-sm btn-success"
hx-post="{% url 'update_feed' feeding.id %}"
hx-target="#feeding-{{ feeding.id }}"
hx-swap="outerHTML"
hx-include="[name='timestamp'], [name='amount_or_duration'], [name='notes']">
<i class="fas fa-save"></i> Save
</button>
<button class="btn btn-sm btn-danger"
hx-get="{% url 'cancel_edit_feed' feeding.id %}"
hx-target="#feeding-{{ feeding.id }}"
hx-swap="outerHTML">
<i class="fas fa-times"></i> Cancel
</button>
</td>
</tr>

View File

@ -0,0 +1,35 @@
<tr id="potty-{{ potty.id }}">
<td>
<input type="datetime-local" name="timestamp" value="{{ potty.timestamp|date:'Y-m-d\\TH:i' }}" class="form-control">
</td>
<td>
<select name="consistency" class="form-select">
{% for value, label in form.consistency.field.choices %}
{% if potty.consistency == value %}
<option value="{{ value }}" selected>{{label}}</option>
{% else %}
<option value="{{ value }}">{{label}}</option>
{% endif %}
{% endfor %}
</select>
</td>
<td>
<textarea name="notes" rows="3" class="form-control form-control-lg">{{ potty.notes }}</textarea>
</td>
<td>
<button class="btn btn-sm btn-success"
hx-post="{% url 'update_potty' potty.id %}"
hx-target="#potty-{{ potty.id }}"
hx-swap="outerHTML"
hx-include="[name='timestamp'], [name='consistency'], [name='notes']">
<i class="fas fa-save"></i> Save
</button>
<button class="btn btn-sm btn-danger"
hx-get="{% url 'cancel_edit_potty' potty.id %}"
hx-target="#potty-{{ potty.id }}"
hx-swap="outerHTML">
<i class="fas fa-times"></i> Cancel
</button>
</td>
</tr>

View File

@ -0,0 +1,30 @@
<tr id="feeding-{{ feeding.id }}">
<th scope="row">{{ feeding.timestamp }}</th>
<td>
{% if feeding.feeding_type == 'breast' %}
{{ feeding.duration }} min
{% else %}
{{ feeding.amount }} oz
{% endif %}
</td>
<td>{{ feeding.notes }}</td>
<td>
<!-- Edit Button -->
<button class="btn btn-sm btn-warning"
hx-get="{% url 'edit_feed' feeding.id %}"
hx-target="#feeding-{{ feeding.id }}"
hx-swap="outerHTML">
<i class="fas fa-edit"></i> <!-- Font Awesome edit icon -->
</button>
<!-- Delete Button -->
<button class="btn btn-sm btn-danger"
hx-delete="{% url 'delete_feed' feeding.id %}"
hx-target="#feed-section"
hx-confirm="Are you sure you want to delete this feed?"
hx-swap="outerHTML">
<i class="fas fa-trash"></i> <!-- Font Awesome trash icon -->
</button>
</td>
</tr>

View File

@ -0,0 +1,58 @@
<section id="feed-section">
<div class="container">
<h2>Feed Records</h2>
<button class="btn btn-primary my-2"
hx-get="{% url 'add_feed' %}"
hx-target="#feed-form-container"
hx-swap="innerHTML">
Add Feed
</button>
<div id="feed-form-container"></div>
<div class="row table-responsive rounded overflow-y-auto" style="max-height: 40rem;">
<table class="table table-primary align-middle">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Amount or Duration</th>
<th scope="col">Notes</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody class="table-group-divider">
{% for feeding in feedings %}
<tr id="feeding-{{ feeding.id }}">
<th scope="row">{{ feeding.timestamp }}</th>
<td>{% if feeding.feeding_type == 'breast' %}{{feeding.duration}} min{% else %}{{feeding.amount}} oz{% endif %}</td>
<td>{{feeding.notes}}</td>
<td>
<button class="btn btn-sm btn-warning"
hx-get="{% url 'edit_feed' feeding.id %}"
hx-target="#feeding-{{ feeding.id }}"
hx-swap="outerHTML">
<i class="fas fa-edit"></i> <!-- Font Awesome edit icon -->
</button>
<button class="btn btn-sm btn-danger"
hx-delete="{% url 'delete_feed' feeding.id %}"
hx-target="#feed-section"
hx-confirm="Are you sure you want to delete this feed?"
hx-swap="outerHTML">
<i class="fas fa-trash"></i> <!-- Font Awesome trash icon -->
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a href="{% url 'export_feedings_csv' %}">
<button class="btn btn-info my-2" >
Export Feed Data
</button>
</a>
</div>
</section>

View File

@ -0,0 +1,21 @@
<tr id="potty-{{potty.id}}">
<th scope="row">{{ potty.timestamp }}</th>
<td>{{ potty.consistency }}</td>
<td>{{ potty.notes }}</td>
<td>
<button class="btn btn-sm btn-warning"
hx-get="{% url 'edit_potty' potty.id %}"
hx-target="#potty-{{potty.id}}"
hx-swap="outerHTML"
>
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-danger"
hx-delete="{% url 'delete_potty' potty.id %}"
hx-target="#potty-section"
hx-confirm="Are you sure you want to delete this potty?"
hx-swap="outerHTML">
<i class="fas fa-trash" ></i>
</button>
</td>
</tr>

View File

@ -0,0 +1,51 @@
<section id="potty-section">
<div class="container">
<h2>Potty Records</h2>
<button class="btn btn-primary my-2" hx-get="{% url 'add_potty' %}" hx-target="#potty-form-container" hx-swap="innerHTML">Add Potty</button>
<div id="potty-form-container"></div>
<div class="row table-responsive rounded overflow-y-auto" style="max-height: 40rem;">
<table class="table table-primary align-middle">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Type</th>
<th scope="col">Notes</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody class="table-group-divider">
{% for potty in potties %}
<tr id="potty-{{potty.id}}">
<th scope="row">{{ potty.timestamp }}</th>
<td>{{ potty.consistency }}</td>
<td>{{ potty.notes }}</td>
<td>
<button class="btn btn-sm btn-warning"
hx-get="{% url 'edit_potty' potty.id %}"
hx-target="#potty-{{potty.id}}"
hx-swap="outerHTML"
>
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-danger"
hx-delete="{% url 'delete_potty' potty.id %}"
hx-target="#potty-section"
hx-confirm="Are you sure you want to delete this potty?"
hx-swap="outerHTML">
<i class="fas fa-trash" ></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a href="{% url 'export_potties_csv' %}">
<button class="btn btn-info my-2" >
Export Potty Data
</button>
</a>
</div>
</section>

3
main/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

18
main/urls.py Normal file
View File

@ -0,0 +1,18 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='feedings_list'),
path('add_feed', views.add_feed, name='add_feed'),
path('delete_feed/<int:feed_id>', views.delete_feed, name='delete_feed'),
path('edit_feed/<int:feed_id>', views.edit_feed, name='edit_feed'),
path('update_feed/<int:feed_id>', views.update_feed, name='update_feed'),
path('cancel_edit_feed/<int:feed_id>', views.cancel_edit_feed, name='cancel_edit_feed'),
path('add_potty', views.add_potty, name='add_potty'),
path('delete_potty/<int:potty_id>', views.delete_potty, name='delete_potty'),
path('edit_potty/<int:potty_id>', views.edit_potty, name='edit_potty'),
path('update_potty/<int:potty_id>', views.update_potty, name='update_potty'),
path('cancel_edit_potty/<int:potty_id>', views.cancel_edit_potty, name='cancel_edit_potty'),
path('export_feedings_csv', views.export_feedings_csv, name='export_feedings_csv'),
path('export_potties_csv', views.export_potties_csv, name='export_potties_csv'),
]

191
main/views.py Normal file
View File

@ -0,0 +1,191 @@
from django.shortcuts import render, get_object_or_404
from .models import Feeding, Potty
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from .forms import FeedingForm, PottyForm
def index(request):
context = {}
feedings = Feeding.objects.all().order_by('-timestamp')
context['feedings'] = feedings
# Grab all objects sorted by timestamp (most recent first)
potties = Potty.objects.all().order_by('-timestamp')
context['potties'] = potties
return render(request, 'main/index.html', context)
def add_feed(request):
context = {}
if request.method == 'POST':
form = FeedingForm(request.POST)
if form.is_valid():
feeding_type = form.cleaned_data['feeding_type']
amount = form.cleaned_data['amount']
duration = form.cleaned_data['duration']
# Validate based on feeding type
if feeding_type == 'bottle' and not amount:
form.add_error('amount', 'Amount is required for bottle feeding.')
elif feeding_type == 'breast' and not duration:
form.add_error('duration', 'Duration is required for breast feeding.')
else:
form.save()
feedings = Feeding.objects.all().order_by('-timestamp')
context['feedings'] = feedings
return render(request, 'main/partials/feed_section.html', context)
else:
form = FeedingForm()
return render(request, 'main/partials/add_feed.html', {'form': form})
@csrf_exempt
def delete_feed(request, feed_id):
context = {}
if request.method == "DELETE":
feed = get_object_or_404(Feeding, id=int(feed_id))
feed.delete()
feedings = Feeding.objects.all().order_by('-timestamp')
context['feedings'] = feedings
return render(request, 'main/partials/feed_section.html', context) # Return a success response
return HttpResponse(status=405) # Method not allowed
from django.shortcuts import get_object_or_404, render
@csrf_exempt
def edit_feed(request, feed_id):
feed = get_object_or_404(Feeding, id=feed_id)
return render(request, 'main/partials/edit_feed_row.html', {'feeding': feed})
@csrf_exempt
def update_feed(request, feed_id):
if request.method == "POST":
feed = get_object_or_404(Feeding, id=feed_id)
feed.timestamp = request.POST['timestamp']
if feed.feeding_type == 'bottle':
feed.amount = float(request.POST['amount_or_duration'])
else:
feed.duration = int(float(request.POST['amount_or_duration']))
feed.notes = request.POST['notes']
feed.save()
return render(request, 'main/partials/feed_row.html', {'feeding': feed})
def cancel_edit_feed(request, feed_id):
feed = get_object_or_404(Feeding, id=feed_id)
return render(request, 'main/partials/feed_row.html', {'feeding': feed})
def add_potty(request):
context = {}
if request.method == 'POST':
form = PottyForm(request.POST)
if form.is_valid():
form.save()
potties = Potty.objects.all().order_by('-timestamp')
context['potties'] = potties
return render(request, 'main/partials/potty_section.html', context)
else:
form = PottyForm()
return render(request, 'main/partials/add_potty.html', {'form': form})
@csrf_exempt
def delete_potty(request, potty_id):
context = {}
if request.method == 'DELETE':
potty = get_object_or_404(Potty, id=int(potty_id))
potty.delete()
potties = Potty.objects.all().order_by('-timestamp')
context['potties'] = potties
return render(request, 'main/partials/potty_section.html', context)
return HttpResponse(status=405)
@csrf_exempt
def edit_potty(request, potty_id):
context = {}
potty = get_object_or_404(Potty, id=potty_id)
context['potty'] = potty
form = PottyForm(potty)
context['form'] = form
return render(request, 'main/partials/edit_potty_row.html', context)
@csrf_exempt
def update_potty(request, potty_id):
context = {}
if request.method == 'POST':
potty = get_object_or_404(Potty, id=potty_id)
potty.timestamp = request.POST['timestamp']
potty.consistency = request.POST['consistency']
potty.notes = request.POST['notes']
potty.save()
return render(request, 'main/partials/potty_row.html', {'potty': potty})
def cancel_edit_potty(request, potty_id):
potty = get_object_or_404(Potty, id=potty_id)
return render(request, 'main/partials/potty_row.html', {'potty': potty})
import csv
from django.http import HttpResponse
def export_feedings_csv(request):
# Create the HTTP response with CSV headers
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="feedings.csv"'
# Create a CSV writer
writer = csv.writer(response)
# Write the header row
writer.writerow(['Timestamp', 'Feeding Type', 'Amount', 'Duration', 'Notes'])
# Fetch the data from the database
feedings = Feeding.objects.all().order_by('-timestamp')
for feeding in feedings:
writer.writerow([
feeding.timestamp,
feeding.get_feeding_type_display(), # Use readable choices if applicable
feeding.amount if feeding.amount else "",
feeding.duration if feeding.duration else "",
feeding.notes,
])
return response
def export_potties_csv(request):
# Create the HTTP response with CSV headers
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="potties.csv"'
# Create a CSV writer
writer = csv.writer(response)
# Write the header row
writer.writerow(['Timestamp', 'Consistency', 'Notes'])
# Fetch the potty data from the database
potties = Potty.objects.all().order_by('-timestamp')
for potty in potties:
writer.writerow([
potty.timestamp,
potty.get_consistency_display() if potty.consistency else "",
potty.notes,
])
return response

22
manage.py Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'baby_tracker.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
asgiref==3.8.1
Django==5.1.4
sqlparse==0.5.2
tzdata==2024.2