From 3a5bf60a30d38aaed29da4a1254a742984d4e4b9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:05:47 +0000 Subject: [PATCH] Feat: Initial web version of the habits tracker app This commit introduces the initial web version of the habits tracker application, built with Django. It provides the core functionality for tracking habits, including user authentication, habit management, and repetition logging. Features implemented: - User authentication (login, logout, signup) using django-allauth. - Core habit management features (CRUD operations for habits). - A main dashboard that displays a list of habits and their repetitions for the last 5 days. - A habit details page with a calendar view of repetitions for the current month. - A management command to send email reminders for habits. - Functionality to export habit data to a CSV file. - Unit tests for the core features. This version addresses the feedback from the code review, including fixing the template structure, implementing repetition logging, and fixing the calendar view. --- .gitignore | 5 + habits_tracker/habits/__init__.py | 0 habits_tracker/habits/admin.py | 3 + habits_tracker/habits/apps.py | 6 + .../management/commands/send_reminders.py | 25 ++++ .../habits/migrations/0001_initial.py | 63 ++++++++ ...habit_reminder_time_habit_send_reminder.py | 23 +++ habits_tracker/habits/migrations/__init__.py | 0 habits_tracker/habits/models.py | 21 +++ .../habits/habit_confirm_delete.html | 10 ++ .../habits/templates/habits/habit_detail.html | 36 +++++ .../habits/templates/habits/habit_form.html | 10 ++ .../habits/templates/habits/habit_list.html | 45 ++++++ .../habits/templatetags/habit_tags.py | 7 + habits_tracker/habits/tests.py | 43 ++++++ habits_tracker/habits/urls.py | 14 ++ habits_tracker/habits/views.py | 102 +++++++++++++ habits_tracker/habits_tracker/__init__.py | 0 habits_tracker/habits_tracker/asgi.py | 16 ++ habits_tracker/habits_tracker/settings.py | 140 ++++++++++++++++++ habits_tracker/habits_tracker/urls.py | 25 ++++ habits_tracker/habits_tracker/wsgi.py | 16 ++ habits_tracker/manage.py | 22 +++ habits_tracker/templates/account/login.html | 10 ++ habits_tracker/templates/account/signup.html | 10 ++ habits_tracker/templates/base.html | 53 +++++++ 26 files changed, 705 insertions(+) create mode 100644 habits_tracker/habits/__init__.py create mode 100644 habits_tracker/habits/admin.py create mode 100644 habits_tracker/habits/apps.py create mode 100644 habits_tracker/habits/management/commands/send_reminders.py create mode 100644 habits_tracker/habits/migrations/0001_initial.py create mode 100644 habits_tracker/habits/migrations/0002_habit_reminder_time_habit_send_reminder.py create mode 100644 habits_tracker/habits/migrations/__init__.py create mode 100644 habits_tracker/habits/models.py create mode 100644 habits_tracker/habits/templates/habits/habit_confirm_delete.html create mode 100644 habits_tracker/habits/templates/habits/habit_detail.html create mode 100644 habits_tracker/habits/templates/habits/habit_form.html create mode 100644 habits_tracker/habits/templates/habits/habit_list.html create mode 100644 habits_tracker/habits/templatetags/habit_tags.py create mode 100644 habits_tracker/habits/tests.py create mode 100644 habits_tracker/habits/urls.py create mode 100644 habits_tracker/habits/views.py create mode 100644 habits_tracker/habits_tracker/__init__.py create mode 100644 habits_tracker/habits_tracker/asgi.py create mode 100644 habits_tracker/habits_tracker/settings.py create mode 100644 habits_tracker/habits_tracker/urls.py create mode 100644 habits_tracker/habits_tracker/wsgi.py create mode 100755 habits_tracker/manage.py create mode 100644 habits_tracker/templates/account/login.html create mode 100644 habits_tracker/templates/account/signup.html create mode 100644 habits_tracker/templates/base.html diff --git a/.gitignore b/.gitignore index 8dac68273..2fc3378cc 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ node_modules *.sketch crowdin.yml kotlin-js-store + +# Python / Django +db.sqlite3 +__pycache__/ +*.pyc diff --git a/habits_tracker/habits/__init__.py b/habits_tracker/habits/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/habits_tracker/habits/admin.py b/habits_tracker/habits/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/habits_tracker/habits/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/habits_tracker/habits/apps.py b/habits_tracker/habits/apps.py new file mode 100644 index 000000000..8a1905717 --- /dev/null +++ b/habits_tracker/habits/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HabitsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "habits" diff --git a/habits_tracker/habits/management/commands/send_reminders.py b/habits_tracker/habits/management/commands/send_reminders.py new file mode 100644 index 000000000..54f975983 --- /dev/null +++ b/habits_tracker/habits/management/commands/send_reminders.py @@ -0,0 +1,25 @@ +from django.core.management.base import BaseCommand +from django.core.mail import send_mail +from django.utils import timezone +from habits.models import Habit + +class Command(BaseCommand): + help = 'Sends habit reminders to users' + + def handle(self, *args, **options): + now = timezone.now() + habits_to_remind = Habit.objects.filter( + send_reminder=True, + reminder_time__hour=now.hour, + reminder_time__minute=now.minute + ) + + for habit in habits_to_remind: + send_mail( + f'Reminder: {habit.name}', + f'This is a reminder to complete your habit: {habit.name}', + 'from@example.com', + [habit.user.email], + fail_silently=False, + ) + self.stdout.write(self.style.SUCCESS(f'Successfully sent reminder for "{habit.name}" to {habit.user.email}')) diff --git a/habits_tracker/habits/migrations/0001_initial.py b/habits_tracker/habits/migrations/0001_initial.py new file mode 100644 index 000000000..37c622139 --- /dev/null +++ b/habits_tracker/habits/migrations/0001_initial.py @@ -0,0 +1,63 @@ +# Generated by Django 5.2.6 on 2025-09-13 17:33 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Habit", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("description", models.TextField(blank=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="Repetition", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date", models.DateField()), + ("value", models.IntegerField(default=1)), + ( + "habit", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="habits.habit" + ), + ), + ], + ), + ] diff --git a/habits_tracker/habits/migrations/0002_habit_reminder_time_habit_send_reminder.py b/habits_tracker/habits/migrations/0002_habit_reminder_time_habit_send_reminder.py new file mode 100644 index 000000000..f6c621501 --- /dev/null +++ b/habits_tracker/habits/migrations/0002_habit_reminder_time_habit_send_reminder.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.6 on 2025-09-13 17:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("habits", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="habit", + name="reminder_time", + field=models.TimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="habit", + name="send_reminder", + field=models.BooleanField(default=False), + ), + ] diff --git a/habits_tracker/habits/migrations/__init__.py b/habits_tracker/habits/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/habits_tracker/habits/models.py b/habits_tracker/habits/models.py new file mode 100644 index 000000000..00efe7ac7 --- /dev/null +++ b/habits_tracker/habits/models.py @@ -0,0 +1,21 @@ +from django.db import models +from django.contrib.auth.models import User + +class Habit(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + send_reminder = models.BooleanField(default=False) + reminder_time = models.TimeField(null=True, blank=True) + + def __str__(self): + return self.name + +class Repetition(models.Model): + habit = models.ForeignKey(Habit, on_delete=models.CASCADE) + date = models.DateField() + value = models.IntegerField(default=1) + + def __str__(self): + return f"{self.habit.name} - {self.date}" diff --git a/habits_tracker/habits/templates/habits/habit_confirm_delete.html b/habits_tracker/habits/templates/habits/habit_confirm_delete.html new file mode 100644 index 000000000..22b2ef2f1 --- /dev/null +++ b/habits_tracker/habits/templates/habits/habit_confirm_delete.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

Are you sure you want to delete "{{ object.name }}"?

+
+ {% csrf_token %} + + Cancel +
+{% endblock %} diff --git a/habits_tracker/habits/templates/habits/habit_detail.html b/habits_tracker/habits/templates/habits/habit_detail.html new file mode 100644 index 000000000..ecf4f3a62 --- /dev/null +++ b/habits_tracker/habits/templates/habits/habit_detail.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block content %} +

{{ object.name }}

+

{{ object.description }}

+ +

Calendar

+ + + + + + + + + + + + + + {% for week in month_days %} + + {% for day in week %} + + {% endfor %} + + {% endfor %} + +
MonTueWedThuFriSatSun
+ {% if day.month == current_month %} + {{ day.day }} + {% endif %} +
+ + Back to Habits +{% endblock %} diff --git a/habits_tracker/habits/templates/habits/habit_form.html b/habits_tracker/habits/templates/habits/habit_form.html new file mode 100644 index 000000000..86eb83d73 --- /dev/null +++ b/habits_tracker/habits/templates/habits/habit_form.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

{% if object %}Edit Habit{% else %}Create Habit{% endif %}

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/habits_tracker/habits/templates/habits/habit_list.html b/habits_tracker/habits/templates/habits/habit_list.html new file mode 100644 index 000000000..2ff35f88c --- /dev/null +++ b/habits_tracker/habits/templates/habits/habit_list.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% load habit_tags %} + +{% block content %} +

My Habits

+ Create New Habit + Export to CSV + + + + + {% for day in days %} + + {% endfor %} + + + + {% for habit, reps in habits_with_reps %} + + + {% for day in days %} + + {% endfor %} + + {% endfor %} + +
Habit{{ day|date:"D" }}
+ {{ habit.name }} + Edit + Delete + +
+ {% csrf_token %} + + + +
+
+{% endblock %} diff --git a/habits_tracker/habits/templatetags/habit_tags.py b/habits_tracker/habits/templatetags/habit_tags.py new file mode 100644 index 000000000..4b04b5c64 --- /dev/null +++ b/habits_tracker/habits/templatetags/habit_tags.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter +def get_item(dictionary, key): + return dictionary.get(key) diff --git a/habits_tracker/habits/tests.py b/habits_tracker/habits/tests.py new file mode 100644 index 000000000..014d555cd --- /dev/null +++ b/habits_tracker/habits/tests.py @@ -0,0 +1,43 @@ +from django.test import TestCase +from django.contrib.auth.models import User +from .models import Habit, Repetition +from django.urls import reverse + +class HabitTestCase(TestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='password') + self.client.login(username='testuser', password='password') + self.habit = Habit.objects.create(user=self.user, name='Test Habit') + self.repetition = Repetition.objects.create(habit=self.habit, date='2025-01-01') + + def test_habit_list_view(self): + response = self.client.get(reverse('habits:habit_list')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Test Habit') + + def test_habit_detail_view(self): + response = self.client.get(reverse('habits:habit_detail', args=[self.habit.pk])) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Test Habit') + + def test_habit_create_view(self): + response = self.client.post(reverse('habits:habit_create'), {'name': 'New Habit'}) + self.assertEqual(response.status_code, 302) + self.assertTrue(Habit.objects.filter(name='New Habit').exists()) + + def test_habit_update_view(self): + response = self.client.post(reverse('habits:habit_update', args=[self.habit.pk]), {'name': 'Updated Habit'}) + self.assertEqual(response.status_code, 302) + self.habit.refresh_from_db() + self.assertEqual(self.habit.name, 'Updated Habit') + + def test_habit_delete_view(self): + response = self.client.post(reverse('habits:habit_delete', args=[self.habit.pk])) + self.assertEqual(response.status_code, 302) + self.assertFalse(Habit.objects.filter(pk=self.habit.pk).exists()) + + def test_export_csv_view(self): + response = self.client.get(reverse('habits:export_csv')) + self.assertEqual(response.status_code, 200) + self.assertEqual(response['Content-Type'], 'text/csv') + self.assertIn('Test Habit,2025-01-01,1', response.content.decode()) diff --git a/habits_tracker/habits/urls.py b/habits_tracker/habits/urls.py new file mode 100644 index 000000000..3c62a7a79 --- /dev/null +++ b/habits_tracker/habits/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from . import views + +app_name = 'habits' + +urlpatterns = [ + path('', views.HabitListView.as_view(), name='habit_list'), + path('/', views.HabitDetailView.as_view(), name='habit_detail'), + path('create/', views.HabitCreateView.as_view(), name='habit_create'), + path('/update/', views.HabitUpdateView.as_view(), name='habit_update'), + path('/delete/', views.HabitDeleteView.as_view(), name='habit_delete'), + path('export/csv/', views.ExportCSVView.as_view(), name='export_csv'), + path('log/', views.LogRepetitionView.as_view(), name='log_repetition'), +] diff --git a/habits_tracker/habits/views.py b/habits_tracker/habits/views.py new file mode 100644 index 000000000..a4e4d54b0 --- /dev/null +++ b/habits_tracker/habits/views.py @@ -0,0 +1,102 @@ +from django.shortcuts import render +from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView, View +from django.contrib.auth.mixins import LoginRequiredMixin +from .models import Habit +from django.urls import reverse_lazy +import csv +from django.http import HttpResponse + +import datetime +from django.utils import timezone + +class HabitListView(LoginRequiredMixin, ListView): + model = Habit + template_name = 'habits/habit_list.html' + + def get_queryset(self): + return Habit.objects.filter(user=self.request.user) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + today = timezone.now().date() + days = [today - datetime.timedelta(days=i) for i in range(5)] + context['days'] = days + + habits_with_reps = [] + for habit in context['object_list']: + reps = {rep.date: rep for rep in habit.repetition_set.filter(date__in=days)} + habits_with_reps.append((habit, reps)) + + context['habits_with_reps'] = habits_with_reps + return context + +import calendar + +class HabitDetailView(LoginRequiredMixin, DetailView): + model = Habit + template_name = 'habits/habit_detail.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + today = timezone.now().date() + cal = calendar.Calendar() + month_days = cal.monthdatescalendar(today.year, today.month) + + reps = {rep.date for rep in self.object.repetition_set.filter(date__year=today.year, date__month=today.month)} + + context['month_days'] = month_days + context['reps'] = reps + context['current_month'] = today.month + return context + +class HabitCreateView(LoginRequiredMixin, CreateView): + model = Habit + fields = ['name', 'description'] + template_name = 'habits/habit_form.html' + success_url = reverse_lazy('habits:habit_list') + + def form_valid(self, form): + form.instance.user = self.request.user + return super().form_valid(form) + +class HabitUpdateView(LoginRequiredMixin, UpdateView): + model = Habit + fields = ['name', 'description'] + template_name = 'habits/habit_form.html' + success_url = reverse_lazy('habits:habit_list') + +class HabitDeleteView(LoginRequiredMixin, DeleteView): + model = Habit + template_name = 'habits/habit_confirm_delete.html' + success_url = reverse_lazy('habits:habit_list') + +from django.shortcuts import get_object_or_404 +from .models import Repetition + +class ExportCSVView(LoginRequiredMixin, View): + def get(self, request, *args, **kwargs): + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="habits.csv"' + + writer = csv.writer(response) + writer.writerow(['Habit', 'Date', 'Value']) + + habits = Habit.objects.filter(user=request.user) + for habit in habits: + for repetition in habit.repetition_set.all(): + writer.writerow([habit.name, repetition.date, repetition.value]) + + return response + +class LogRepetitionView(LoginRequiredMixin, View): + def post(self, request, *args, **kwargs): + habit_id = request.POST.get('habit_id') + date_str = request.POST.get('date') + date = datetime.datetime.strptime(date_str, '%Y-%m-%d').date() + habit = get_object_or_404(Habit, pk=habit_id, user=request.user) + + rep, created = Repetition.objects.get_or_create(habit=habit, date=date) + if not created: + rep.delete() + + return HttpResponse(status=204) diff --git a/habits_tracker/habits_tracker/__init__.py b/habits_tracker/habits_tracker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/habits_tracker/habits_tracker/asgi.py b/habits_tracker/habits_tracker/asgi.py new file mode 100644 index 000000000..7097ebeca --- /dev/null +++ b/habits_tracker/habits_tracker/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for habits_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.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "habits_tracker.settings") + +application = get_asgi_application() diff --git a/habits_tracker/habits_tracker/settings.py b/habits_tracker/habits_tracker/settings.py new file mode 100644 index 000000000..e369c32e3 --- /dev/null +++ b/habits_tracker/habits_tracker/settings.py @@ -0,0 +1,140 @@ +""" +Django settings for habits_tracker project. + +Generated by 'django-admin startproject' using Django 5.2.6. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/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.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-ck4e+hvwh86i8b*-v0d0uwn$le-o=qvu)f(ss(%6arverkar8f" + +# 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", + "habits", + # The following apps are required by django-allauth + 'allauth', + 'allauth.account', + 'allauth.socialaccount', +] + +AUTHENTICATION_BACKENDS = [ + # Needed to login by username in Django admin, regardless of `allauth` + 'django.contrib.auth.backends.ModelBackend', + + # `allauth` specific authentication methods, such as login by e-mail + 'allauth.account.auth_backends.AuthenticationBackend', +] + +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", + "allauth.account.middleware.AccountMiddleware", +] + +ROOT_URLCONF = "habits_tracker.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / 'templates'], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "habits_tracker.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/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.2/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +SITE_ID = 1 + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/habits_tracker/habits_tracker/urls.py b/habits_tracker/habits_tracker/urls.py new file mode 100644 index 000000000..07ec761a8 --- /dev/null +++ b/habits_tracker/habits_tracker/urls.py @@ -0,0 +1,25 @@ +""" +URL configuration for habits_tracker project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/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("accounts/", include("allauth.urls")), + path("habits/", include("habits.urls", namespace="habits")), +] diff --git a/habits_tracker/habits_tracker/wsgi.py b/habits_tracker/habits_tracker/wsgi.py new file mode 100644 index 000000000..b1a5c65c7 --- /dev/null +++ b/habits_tracker/habits_tracker/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for habits_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.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "habits_tracker.settings") + +application = get_wsgi_application() diff --git a/habits_tracker/manage.py b/habits_tracker/manage.py new file mode 100755 index 000000000..f71e1130c --- /dev/null +++ b/habits_tracker/manage.py @@ -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", "habits_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() diff --git a/habits_tracker/templates/account/login.html b/habits_tracker/templates/account/login.html new file mode 100644 index 000000000..b7050086b --- /dev/null +++ b/habits_tracker/templates/account/login.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

Log In

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/habits_tracker/templates/account/signup.html b/habits_tracker/templates/account/signup.html new file mode 100644 index 000000000..afcc7755e --- /dev/null +++ b/habits_tracker/templates/account/signup.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

Sign Up

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/habits_tracker/templates/base.html b/habits_tracker/templates/base.html new file mode 100644 index 000000000..6eb102623 --- /dev/null +++ b/habits_tracker/templates/base.html @@ -0,0 +1,53 @@ + + + + Habits Tracker + + + + +
+ {% block content %} + {% endblock %} + + + +