From 5b57790e7940ee4747b0c71f4e5253d719f4ed1d Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 12 Jan 2026 11:57:47 +0100 Subject: [PATCH 001/113] Initial commit --- .idea/.gitignore | 3 ++ .idea/Python-OC-Lettings-FR.iml | 14 ++++++ .idea/inspectionProfiles/Project_Default.xml | 49 +++++++++++++++++++ .../inspectionProfiles/profiles_settings.xml | 6 +++ .idea/misc.xml | 4 ++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 +++ 7 files changed, 90 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/Python-OC-Lettings-FR.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..26d33521af --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/Python-OC-Lettings-FR.iml b/.idea/Python-OC-Lettings-FR.iml new file mode 100644 index 0000000000..7a6134d11f --- /dev/null +++ b/.idea/Python-OC-Lettings-FR.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000..41c0bfed07 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,49 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..105ce2da2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..ab51480957 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..503d3d9141 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..35eb1ddfbb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From c5469b3697e2a6222ab576bd101d08659e2bcb27 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 12 Jan 2026 12:04:42 +0100 Subject: [PATCH 002/113] Initial commit --- .gitignore | 3 +++ .idea/misc.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index b4405ebab4..68500e8a60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ **/__pycache__ *.pyc venv +.idea/ +.env +*.log \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index ab51480957..93cce1b3d9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ + + \ No newline at end of file From 2ca2a70898d179d58dd0684310fc7f4316fa8b38 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 12 Jan 2026 17:03:59 +0100 Subject: [PATCH 003/113] First linting updates/ compatibility updates. --- oc_lettings_site/migrations/0001_initial.py | 91 +++++++++++++++++---- oc_lettings_site/settings.py | 84 +++++++++---------- oc_lettings_site/views.py | 66 +++++++++------ requirements.txt | 2 +- 4 files changed, 158 insertions(+), 85 deletions(-) diff --git a/oc_lettings_site/migrations/0001_initial.py b/oc_lettings_site/migrations/0001_initial.py index 774cf23f58..a076830454 100644 --- a/oc_lettings_site/migrations/0001_initial.py +++ b/oc_lettings_site/migrations/0001_initial.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -16,31 +15,89 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Address', + name="Address", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('number', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(9999)])), - ('street', models.CharField(max_length=64)), - ('city', models.CharField(max_length=64)), - ('state', models.CharField(max_length=2, validators=[django.core.validators.MinLengthValidator(2)])), - ('zip_code', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(99999)])), - ('country_iso_code', models.CharField(max_length=3, validators=[django.core.validators.MinLengthValidator(3)])), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "number", + models.PositiveIntegerField( + validators=[django.core.validators.MaxValueValidator(9999)] + ), + ), + ("street", models.CharField(max_length=64)), + ("city", models.CharField(max_length=64)), + ( + "state", + models.CharField( + max_length=2, + validators=[django.core.validators.MinLengthValidator(2)], + ), + ), + ( + "zip_code", + models.PositiveIntegerField( + validators=[django.core.validators.MaxValueValidator(99999)] + ), + ), + ( + "country_iso_code", + models.CharField( + max_length=3, + validators=[django.core.validators.MinLengthValidator(3)], + ), + ), ], ), migrations.CreateModel( - name='Profile', + name="Profile", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('favorite_city', models.CharField(blank=True, max_length=64)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("favorite_city", models.CharField(blank=True, max_length=64)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='Letting', + name="Letting", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=256)), - ('address', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='oc_lettings_site.Address')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=256)), + ( + "address", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to="oc_lettings_site.Address", + ), + ), ], ), ] diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index a18bee8106..42ca3b859b 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -5,98 +5,93 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'fp$9^593hsriajg$_%=5trot9g!1qa@ew(o-1#@=&4%=hp46(s' +SECRET_KEY = "fp$9^593hsriajg$_%=5trot9g!1qa@ew(o-1#@=&4%=hp46(s" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] - # Application definition INSTALLED_APPS = [ - 'oc_lettings_site.apps.OCLettingsSiteConfig', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "oc_lettings_site.apps.OCLettingsSiteConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", ] 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', + "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 = 'oc_lettings_site.urls' +ROOT_URLCONF = "oc_lettings_site.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - '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', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "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 = 'oc_lettings_site.wsgi.application' - +WSGI_APPLICATION = "oc_lettings_site.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'oc-lettings-site.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "oc-lettings-site.sqlite3"), } } - # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] - # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -104,11 +99,12 @@ USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") -STATIC_URL = '/static/' -STATICFILES_DIRS = [BASE_DIR / "static",] +STATIC_URL = "/static/" +STATICFILES_DIRS = [ + BASE_DIR / "static", +] diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index a72db27074..71aace9d77 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -2,44 +2,64 @@ from .models import Letting, Profile - - -# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque molestie quam lobortis leo consectetur ullamcorper non id est. Praesent dictum, nulla eget feugiat sagittis, sem mi convallis eros, -# vitae dapibus nisi lorem dapibus sem. Maecenas pharetra purus ipsum, eget consequat ipsum lobortis quis. Phasellus eleifend ex auctor venenatis tempus. -# Aliquam vitae erat ac orci placerat luctus. Nullam elementum urna nisi, pellentesque iaculis enim cursus in. Praesent volutpat porttitor magna, non finibus neque cursus id. +# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque molestie quam lobortis leo +# consectetur ullamcorper non id est. Praesent dictum, nulla eget feugiat sagittis, sem mi +# convallis eros, +# vitae dapibus nisi lorem dapibus sem. Maecenas pharetra purus ipsum, eget consequat ipsum +# lobortis quis. Phasellus eleifend ex auctor venenatis tempus. +# Aliquam vitae erat ac orci placerat luctus. Nullam elementum urna nisi, pellentesque iaculis +# enim cursus in. Praesent volutpat porttitor magna, non finibus neque cursus id. def index(request): - return render(request, 'index.html') + return render(request, "index.html") + -# Aenean leo magna, vestibulum et tincidunt fermentum, consectetur quis velit. Sed non placerat massa. Integer est nunc, pulvinar a -# tempor et, bibendum id arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras eget scelerisque +# Aenean leo magna, vestibulum et tincidunt fermentum, consectetur quis velit. +# Sed non placerat massa. Integer est nunc, pulvinar a +# tempor et, bibendum id arcu. Vestibulum ante ipsum primis in faucibus orci luctus et +# ultrices posuere cubilia curae; Cras eget scelerisque def lettings_index(request): lettings_list = Letting.objects.all() - context = {'lettings_list': lettings_list} - return render(request, 'lettings_index.html', context) + context = {"lettings_list": lettings_list} + return render(request, "lettings_index.html", context) -#Cras ultricies dignissim purus, vitae hendrerit ex varius non. In accumsan porta nisl id eleifend. Praesent dignissim, odio eu consequat pretium, purus urna vulputate arcu, vitae efficitur -# lacus justo nec purus. Aenean finibus faucibus lectus at porta. Maecenas auctor, est ut luctus congue, dui enim mattis enim, ac condimentum velit libero in magna. Suspendisse potenti. In tempus a nisi sed laoreet. -# Suspendisse porta dui eget sem accumsan interdum. Ut quis urna pellentesque justo mattis ullamcorper ac non tellus. In tristique mauris eu velit fermentum, tempus pharetra est luctus. Vivamus consequat aliquam libero, eget bibendum lorem. Sed non dolor risus. Mauris condimentum auctor elementum. Donec quis nisi ligula. Integer vehicula tincidunt enim, ac lacinia augue pulvinar sit amet. +# Cras ultricies dignissim purus, vitae hendrerit ex varius non. +# In accumsan porta nisl id eleifend. +# Praesent dignissim, odio eu consequat pretium, purus urna vulputate arcu, vitae efficitur +# lacus justo nec purus. Aenean finibus faucibus lectus at porta. +# Maecenas auctor, est ut luctus congue, dui enim mattis enim, ac condimentum velit libero +# in magna. +# Suspendisse potenti. In tempus a nisi sed laoreet. +# Suspendisse porta dui eget sem accumsan interdum. Ut quis urna pellentesque justo mattis +# ullamcorper ac non tellus. +# In tristique mauris eu velit fermentum, tempus pharetra est luctus. +# Vivamus consequat aliquam libero, eget bibendum lorem. Sed non dolor risus. +# Mauris condimentum auctor elementum. Donec quis nisi ligula. Integer vehicula tincidunt enim, +# ac lacinia augue pulvinar sit amet. def letting(request, letting_id): letting = Letting.objects.get(id=letting_id) context = { - 'title': letting.title, - 'address': letting.address, + "title": letting.title, + "address": letting.address, } - return render(request, 'letting.html', context) + return render(request, "letting.html", context) -# Sed placerat quam in pulvinar commodo. Nullam laoreet consectetur ex, sed consequat libero pulvinar eget. Fusc + +# Sed placerat quam in pulvinar commodo. Nullam laoreet consectetur ex, sed consequat libero +# pulvinar eget. Fusc # faucibus, urna quis auctor pharetra, massa dolor cursus neque, quis dictum lacus d def profiles_index(request): profiles_list = Profile.objects.all() - context = {'profiles_list': profiles_list} - return render(request, 'profiles_index.html', context) + context = {"profiles_list": profiles_list} + return render(request, "profiles_index.html", context) + # Aliquam sed metus eget nisi tincidunt ornare accumsan eget lac -# laoreet neque quis, pellentesque dui. Nullam facilisis pharetra vulputate. Sed tincidunt, dolor id facilisis fringilla, eros leo tristique lacus, -# it. Nam aliquam dignissim congue. Pellentesque habitant morbi tristique senectus et netus et males +# laoreet neque quis, pellentesque dui. Nullam facilisis pharetra vulputate. +# Sed tincidunt, dolor id facilisis fringilla, eros leo tristique lacus, +# it. Nam aliquam dignissim congue. Pellentesque habitant morbi tristique senectus et netus +# et males def profile(request, username): profile = Profile.objects.get(user__username=username) - context = {'profile': profile} - return render(request, 'profile.html', context) + context = {"profile": profile} + return render(request, "profile.html", context) diff --git a/requirements.txt b/requirements.txt index c48c84ea40..f6acb73f85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ django==3.0 -flake8==3.7.0 +flake8==3.8.4 pytest-django==3.9.0 \ No newline at end of file From a7de62b84a307b225b5e85218aa2fb9e2124606c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 23 Jan 2026 09:55:52 +0100 Subject: [PATCH 004/113] Created lettings app --- lettings/__init__.py | 0 lettings/admin.py | 3 +++ lettings/apps.py | 5 +++++ lettings/migrations/__init__.py | 0 lettings/models.py | 3 +++ lettings/tests.py | 3 +++ lettings/views.py | 3 +++ 7 files changed, 17 insertions(+) create mode 100644 lettings/__init__.py create mode 100644 lettings/admin.py create mode 100644 lettings/apps.py create mode 100644 lettings/migrations/__init__.py create mode 100644 lettings/models.py create mode 100644 lettings/tests.py create mode 100644 lettings/views.py diff --git a/lettings/__init__.py b/lettings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lettings/admin.py b/lettings/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/lettings/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/lettings/apps.py b/lettings/apps.py new file mode 100644 index 0000000000..b6abff1791 --- /dev/null +++ b/lettings/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class LettingsConfig(AppConfig): + name = 'lettings' diff --git a/lettings/migrations/__init__.py b/lettings/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lettings/models.py b/lettings/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/lettings/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/lettings/tests.py b/lettings/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/lettings/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/lettings/views.py b/lettings/views.py new file mode 100644 index 0000000000..91ea44a218 --- /dev/null +++ b/lettings/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 650509089ee89b30c69bcac2b6cbf213a9f30d67 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 23 Jan 2026 09:56:29 +0100 Subject: [PATCH 005/113] Created profiles app --- profiles/__init__.py | 0 profiles/admin.py | 3 +++ profiles/apps.py | 5 +++++ profiles/migrations/__init__.py | 0 profiles/models.py | 3 +++ profiles/tests.py | 3 +++ profiles/views.py | 3 +++ 7 files changed, 17 insertions(+) create mode 100644 profiles/__init__.py create mode 100644 profiles/admin.py create mode 100644 profiles/apps.py create mode 100644 profiles/migrations/__init__.py create mode 100644 profiles/models.py create mode 100644 profiles/tests.py create mode 100644 profiles/views.py diff --git a/profiles/__init__.py b/profiles/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/profiles/admin.py b/profiles/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/profiles/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/profiles/apps.py b/profiles/apps.py new file mode 100644 index 0000000000..5501fdad35 --- /dev/null +++ b/profiles/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ProfilesConfig(AppConfig): + name = 'profiles' diff --git a/profiles/migrations/__init__.py b/profiles/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/profiles/models.py b/profiles/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/profiles/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/profiles/tests.py b/profiles/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/profiles/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/profiles/views.py b/profiles/views.py new file mode 100644 index 0000000000..91ea44a218 --- /dev/null +++ b/profiles/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 990d35362b89242e7f9b733d2893d1230060052d Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 23 Jan 2026 10:57:47 +0100 Subject: [PATCH 006/113] Updated INSTALLED_APPS. --- oc_lettings_site/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 42ca3b859b..3f4fd60f00 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -20,6 +20,8 @@ INSTALLED_APPS = [ "oc_lettings_site.apps.OCLettingsSiteConfig", + "lettings", + "profiles", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", From 665aa1cf1c8a79699fe24ea7a53ec4e885bcc177 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 23 Jan 2026 11:01:49 +0100 Subject: [PATCH 007/113] Remove IDE files from version control. --- .idea/.gitignore | 3 -- .idea/Python-OC-Lettings-FR.iml | 14 ------ .idea/inspectionProfiles/Project_Default.xml | 49 ------------------- .../inspectionProfiles/profiles_settings.xml | 6 --- .idea/misc.xml | 7 --- .idea/modules.xml | 8 --- .idea/vcs.xml | 6 --- 7 files changed, 93 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/Python-OC-Lettings-FR.iml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521af..0000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/Python-OC-Lettings-FR.iml b/.idea/Python-OC-Lettings-FR.iml deleted file mode 100644 index 7a6134d11f..0000000000 --- a/.idea/Python-OC-Lettings-FR.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 41c0bfed07..0000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2da2d..0000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 93cce1b3d9..0000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 503d3d9141..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbb..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From f92bdb4ea3de8a6637d3334e8e5f08d0445e2634 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 26 Jan 2026 08:01:26 +0100 Subject: [PATCH 008/113] Moved models in proper app, and deleted the "old" version. --- lettings/models.py | 21 ++++++++++++++++++++- oc_lettings_site/models.py | 29 ----------------------------- profiles/models.py | 9 ++++++++- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/lettings/models.py b/lettings/models.py index 71a8362390..baa25397f0 100644 --- a/lettings/models.py +++ b/lettings/models.py @@ -1,3 +1,22 @@ from django.db import models +from django.core.validators import MaxValueValidator, MinLengthValidator -# Create your models here. + +class Address(models.Model): + number = models.PositiveIntegerField(validators=[MaxValueValidator(9999)]) + street = models.CharField(max_length=64) + city = models.CharField(max_length=64) + state = models.CharField(max_length=2, validators=[MinLengthValidator(2)]) + zip_code = models.PositiveIntegerField(validators=[MaxValueValidator(99999)]) + country_iso_code = models.CharField(max_length=3, validators=[MinLengthValidator(3)]) + + def __str__(self): + return f"{self.number} {self.street}" + + +class Letting(models.Model): + title = models.CharField(max_length=256) + address = models.OneToOneField(Address, on_delete=models.CASCADE) + + def __str__(self): + return self.title diff --git a/oc_lettings_site/models.py b/oc_lettings_site/models.py index ed255e8c11..beeb308265 100644 --- a/oc_lettings_site/models.py +++ b/oc_lettings_site/models.py @@ -1,31 +1,2 @@ from django.db import models -from django.core.validators import MaxValueValidator, MinLengthValidator -from django.contrib.auth.models import User - -class Address(models.Model): - number = models.PositiveIntegerField(validators=[MaxValueValidator(9999)]) - street = models.CharField(max_length=64) - city = models.CharField(max_length=64) - state = models.CharField(max_length=2, validators=[MinLengthValidator(2)]) - zip_code = models.PositiveIntegerField(validators=[MaxValueValidator(99999)]) - country_iso_code = models.CharField(max_length=3, validators=[MinLengthValidator(3)]) - - def __str__(self): - return f'{self.number} {self.street}' - - -class Letting(models.Model): - title = models.CharField(max_length=256) - address = models.OneToOneField(Address, on_delete=models.CASCADE) - - def __str__(self): - return self.title - - -class Profile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) - favorite_city = models.CharField(max_length=64, blank=True) - - def __str__(self): - return self.user.username diff --git a/profiles/models.py b/profiles/models.py index 71a8362390..84c85c1001 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -1,3 +1,10 @@ from django.db import models +from django.contrib.auth.models import User -# Create your models here. + +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + favorite_city = models.CharField(max_length=64, blank=True) + + def __str__(self): + return self.user.username From a53bca64b30f08ac6d53fe257c7d99c7823a6c2b Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 26 Jan 2026 19:12:46 +0100 Subject: [PATCH 009/113] Corrected import, and transfered admin contents in app-related files. makemigration is now functioning. --- lettings/admin.py | 7 ++++++- oc_lettings_site/admin.py | 6 ------ oc_lettings_site/models.py | 2 -- oc_lettings_site/views.py | 3 ++- profiles/admin.py | 5 ++++- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lettings/admin.py b/lettings/admin.py index 8c38f3f3da..3b3fb925bd 100644 --- a/lettings/admin.py +++ b/lettings/admin.py @@ -1,3 +1,8 @@ from django.contrib import admin -# Register your models here. +from .models import Letting +from .models import Address + + +admin.site.register(Letting) +admin.site.register(Address) diff --git a/oc_lettings_site/admin.py b/oc_lettings_site/admin.py index 63328c6dd3..925adeec99 100644 --- a/oc_lettings_site/admin.py +++ b/oc_lettings_site/admin.py @@ -1,10 +1,4 @@ from django.contrib import admin -from .models import Letting -from .models import Address -from .models import Profile -admin.site.register(Letting) -admin.site.register(Address) -admin.site.register(Profile) diff --git a/oc_lettings_site/models.py b/oc_lettings_site/models.py index beeb308265..e69de29bb2 100644 --- a/oc_lettings_site/models.py +++ b/oc_lettings_site/models.py @@ -1,2 +0,0 @@ -from django.db import models - diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index 71aace9d77..02e9c329a8 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -1,5 +1,6 @@ from django.shortcuts import render -from .models import Letting, Profile +from lettings.models import Letting +from profiles.models import Profile # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque molestie quam lobortis leo diff --git a/profiles/admin.py b/profiles/admin.py index 8c38f3f3da..f045416366 100644 --- a/profiles/admin.py +++ b/profiles/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin -# Register your models here. +from .models import Profile + + +admin.site.register(Profile) From a1a6e0b5295eb0f570efc0e84f07b9d896f2e9c9 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 1 Feb 2026 11:08:13 +0100 Subject: [PATCH 010/113] Restored those file to their ancient state from github. --- oc_lettings_site/admin.py | 6 ++++++ oc_lettings_site/models.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/oc_lettings_site/admin.py b/oc_lettings_site/admin.py index 925adeec99..63328c6dd3 100644 --- a/oc_lettings_site/admin.py +++ b/oc_lettings_site/admin.py @@ -1,4 +1,10 @@ from django.contrib import admin +from .models import Letting +from .models import Address +from .models import Profile +admin.site.register(Letting) +admin.site.register(Address) +admin.site.register(Profile) diff --git a/oc_lettings_site/models.py b/oc_lettings_site/models.py index e69de29bb2..82151ac16a 100644 --- a/oc_lettings_site/models.py +++ b/oc_lettings_site/models.py @@ -0,0 +1,31 @@ +from django.db import models +from django.core.validators import MaxValueValidator, MinLengthValidator +from django.contrib.auth.models import User + + +class Address(models.Model): + number = models.PositiveIntegerField(validators=[MaxValueValidator(9999)]) + street = models.CharField(max_length=64) + city = models.CharField(max_length=64) + state = models.CharField(max_length=2, validators=[MinLengthValidator(2)]) + zip_code = models.PositiveIntegerField(validators=[MaxValueValidator(99999)]) + country_iso_code = models.CharField(max_length=3, validators=[MinLengthValidator(3)]) + + def __str__(self): + return f"{self.number} {self.street}" + + +class Letting(models.Model): + title = models.CharField(max_length=256) + address = models.OneToOneField(Address, on_delete=models.CASCADE) + + def __str__(self): + return self.title + + +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + favorite_city = models.CharField(max_length=64, blank=True) + + def __str__(self): + return self.user.username From 89e2706558e40ba06ef3ac8e8af668081d332a8e Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 1 Feb 2026 11:35:01 +0100 Subject: [PATCH 011/113] Worked out the migration of the tables by pointing to the old ones via Meta db_tables = "". --- lettings/models.py | 8 ++++++++ oc-lettings-site.sqlite3 | Bin 151552 -> 151552 bytes oc_lettings_site/admin.py | 10 ---------- oc_lettings_site/models.py | 31 ------------------------------- profiles/models.py | 5 ++++- 5 files changed, 12 insertions(+), 42 deletions(-) diff --git a/lettings/models.py b/lettings/models.py index baa25397f0..4c83ef240c 100644 --- a/lettings/models.py +++ b/lettings/models.py @@ -9,14 +9,22 @@ class Address(models.Model): state = models.CharField(max_length=2, validators=[MinLengthValidator(2)]) zip_code = models.PositiveIntegerField(validators=[MaxValueValidator(99999)]) country_iso_code = models.CharField(max_length=3, validators=[MinLengthValidator(3)]) + related_name = "new_address" def __str__(self): return f"{self.number} {self.street}" + class Meta: + db_table = "oc_lettings_site_address" + class Letting(models.Model): title = models.CharField(max_length=256) address = models.OneToOneField(Address, on_delete=models.CASCADE) + related_name = "new_letting" def __str__(self): return self.title + + class Meta: + db_table = "oc_lettings_site_letting" diff --git a/oc-lettings-site.sqlite3 b/oc-lettings-site.sqlite3 index 3d885414f9f3ed046704e8b09bdcd5c791c82d42..ebd752d732617890f81eb903a67ee9c21ba744b4 100644 GIT binary patch delta 163 zcmZozz}c{XbAmLZ%|sbzRvQMraIcLi3;3BuxIRy27f@y4*5LZQvGFihmazycgRH7! zK~a8MW=?9cfq{Wxd}dx|NoHb>k%5t!u7Q!RfuVw-ft9h5m64&Isey&5nVGRLSW`}F iNl9j2Izde)mX;>Q7Mo+G7l|;6G#RuRFfKD-QUCyfvnzZ6 delta 51 zcmZozz}c{XbAmLZ)kGO*Rx1WQnGG9L7Vt9*amP+(7f{{UXvn=eR(g>LqfnDUn*rl8 H111Fkgs~1a diff --git a/oc_lettings_site/admin.py b/oc_lettings_site/admin.py index 63328c6dd3..e69de29bb2 100644 --- a/oc_lettings_site/admin.py +++ b/oc_lettings_site/admin.py @@ -1,10 +0,0 @@ -from django.contrib import admin - -from .models import Letting -from .models import Address -from .models import Profile - - -admin.site.register(Letting) -admin.site.register(Address) -admin.site.register(Profile) diff --git a/oc_lettings_site/models.py b/oc_lettings_site/models.py index 82151ac16a..e69de29bb2 100644 --- a/oc_lettings_site/models.py +++ b/oc_lettings_site/models.py @@ -1,31 +0,0 @@ -from django.db import models -from django.core.validators import MaxValueValidator, MinLengthValidator -from django.contrib.auth.models import User - - -class Address(models.Model): - number = models.PositiveIntegerField(validators=[MaxValueValidator(9999)]) - street = models.CharField(max_length=64) - city = models.CharField(max_length=64) - state = models.CharField(max_length=2, validators=[MinLengthValidator(2)]) - zip_code = models.PositiveIntegerField(validators=[MaxValueValidator(99999)]) - country_iso_code = models.CharField(max_length=3, validators=[MinLengthValidator(3)]) - - def __str__(self): - return f"{self.number} {self.street}" - - -class Letting(models.Model): - title = models.CharField(max_length=256) - address = models.OneToOneField(Address, on_delete=models.CASCADE) - - def __str__(self): - return self.title - - -class Profile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) - favorite_city = models.CharField(max_length=64, blank=True) - - def __str__(self): - return self.user.username diff --git a/profiles/models.py b/profiles/models.py index 84c85c1001..c81ae84195 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -3,8 +3,11 @@ class Profile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="new_user") favorite_city = models.CharField(max_length=64, blank=True) def __str__(self): return self.user.username + + class Meta: + db_table = "oc_lettings_site_profile" From 7cd6db1b5bcbcfda3486e1d1ca7ccb253cec1991 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 2 Feb 2026 15:20:32 +0100 Subject: [PATCH 012/113] Added the migration files --- lettings/migrations/0001_initial.py | 12 ++++++++++++ profiles/migrations/0001_initial.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 lettings/migrations/0001_initial.py create mode 100644 profiles/migrations/0001_initial.py diff --git a/lettings/migrations/0001_initial.py b/lettings/migrations/0001_initial.py new file mode 100644 index 0000000000..e44c28e987 --- /dev/null +++ b/lettings/migrations/0001_initial.py @@ -0,0 +1,12 @@ +# Generated by Django 3.0 on 2026-02-01 10:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + ] diff --git a/profiles/migrations/0001_initial.py b/profiles/migrations/0001_initial.py new file mode 100644 index 0000000000..e44c28e987 --- /dev/null +++ b/profiles/migrations/0001_initial.py @@ -0,0 +1,12 @@ +# Generated by Django 3.0 on 2026-02-01 10:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + ] From 4d58c784c5bd317bae77d469fcc8758e91a16b46 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Thu, 5 Feb 2026 10:57:54 +0100 Subject: [PATCH 013/113] Run migration for profile table, sync django and db, and renamed tables. Everything is functioning at this point. --- lettings/models.py | 1 - oc-lettings-site.sqlite3 | Bin 151552 -> 151552 bytes profiles/models.py | 2 +- 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lettings/models.py b/lettings/models.py index 4c83ef240c..183ef6ab27 100644 --- a/lettings/models.py +++ b/lettings/models.py @@ -9,7 +9,6 @@ class Address(models.Model): state = models.CharField(max_length=2, validators=[MinLengthValidator(2)]) zip_code = models.PositiveIntegerField(validators=[MaxValueValidator(99999)]) country_iso_code = models.CharField(max_length=3, validators=[MinLengthValidator(3)]) - related_name = "new_address" def __str__(self): return f"{self.number} {self.street}" diff --git a/oc-lettings-site.sqlite3 b/oc-lettings-site.sqlite3 index ebd752d732617890f81eb903a67ee9c21ba744b4..d9a0ea579cb23506899af06d0d0f5ca440149ffc 100644 GIT binary patch delta 855 zcmZXRO-vI}5Xbki+wS(!c7V3f*c7&q(rsX2KU%(&FA0$*Ciu~d2W$&0LIi5sq8Afc zIFcBfO>Dx!5aR_eu<4;ELrjcb(Qq&l#fS&>YU06zy3{T)^(Aj6zj^b2GjHDNmTh&* z_P|#^LJ&mI*yf7mJ7qMYC!YY>h_f197p8C#XLWTL-y3SQ5dO2Hw=(Iug+(PR2!fnQ zt>n@PNsxL3Nr)yyLGC#LUXXYp$_o7ha^HX~bV#&=I4tZj5pAWl2z z?(jEaZRS1GVEtxwTb9V@X=G7hRaG$m5DqlWsGY4UIm=LQY zZ0YbOqRH#Ve|Vb)2hp0Vmp<|9{khciqLN-C4d!{1tL4?qj9nQYjgPbO(eoF_*;ezo zG?GRyk|c+j0=fQ;kENDbWBzQVOzF`(G`;c%QswI# delta 357 zcmZozz}c{XbAq&>4Fdy%DiFhf$3z`tMw^WZOZb^ZxIRy27tq|;c$jPRXDMM;5eWwV zm;AH%i}?5RZ{Y9d58*fCm)O|&kB?PGfZ3LD`nv>1iOHt=p^Ti9yY)R7RVH82*OnIH z0-DUpZ^FPooxg;?nLmx+lm7|-D*ogACL0^2_$SNTvre|OKP|?|Z_L2|nEx36O8#m5 zP5j0Dsr(-N#v2W*)ISE?(%Kcy?>)Y zli>mbmksAij=LN}Y_HfvSaX>VGWRiUW{zZ3d)E!f19 zZKoUNGD=KepT{V;{eJ@E3r-fGQ@>1X{ILCYHX}PTf0ML1ySS|_W3y*TVp2{j*nbd0 qGoO)vdVV6K;B=rq_U#_|jC&H8Rk-F&XTQJ*bdn#}_IbA$lS%<^_-nQR diff --git a/profiles/models.py b/profiles/models.py index c81ae84195..e2ca495454 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -10,4 +10,4 @@ def __str__(self): return self.user.username class Meta: - db_table = "oc_lettings_site_profile" + db_table = "profiles_profile" From b0b07a894827778b00be0178bd6f9561d9dfe38b Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Thu, 5 Feb 2026 11:06:19 +0100 Subject: [PATCH 014/113] Applied previous migration plan for lettings app, everything seems to be working as attended. --- lettings/migrations/0002_address_letting.py | 43 ++++++++++++++++ .../migrations/0003_auto_20260205_1103.py | 21 ++++++++ lettings/models.py | 4 +- oc-lettings-site.sqlite3 | Bin 151552 -> 151552 bytes .../migrations/0002_auto_20260205_1014.py | 47 ++++++++++++++++++ .../migrations/0003_auto_20260205_1036.py | 17 +++++++ 6 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 lettings/migrations/0002_address_letting.py create mode 100644 lettings/migrations/0003_auto_20260205_1103.py create mode 100644 profiles/migrations/0002_auto_20260205_1014.py create mode 100644 profiles/migrations/0003_auto_20260205_1036.py diff --git a/lettings/migrations/0002_address_letting.py b/lettings/migrations/0002_address_letting.py new file mode 100644 index 0000000000..46fcc82e76 --- /dev/null +++ b/lettings/migrations/0002_address_letting.py @@ -0,0 +1,43 @@ +# Generated by Django 3.0 on 2026-02-05 09:49 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('lettings', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Address', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(9999)])), + ('street', models.CharField(max_length=64)), + ('city', models.CharField(max_length=64)), + ('state', models.CharField(max_length=2, validators=[django.core.validators.MinLengthValidator(2)])), + ('zip_code', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(99999)])), + ('country_iso_code', models.CharField(max_length=3, validators=[django.core.validators.MinLengthValidator(3)])), + ], + options={ + 'db_table': 'oc_lettings_site_address', + }, + ), + migrations.CreateModel( + name='Letting', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=256)), + ('address', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='lettings.Address')), + ], + options={ + 'db_table': 'oc_lettings_site_letting', + }, + ), + ] diff --git a/lettings/migrations/0003_auto_20260205_1103.py b/lettings/migrations/0003_auto_20260205_1103.py new file mode 100644 index 0000000000..b607397132 --- /dev/null +++ b/lettings/migrations/0003_auto_20260205_1103.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0 on 2026-02-05 10:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('lettings', '0002_address_letting'), + ] + + operations = [ + migrations.AlterModelTable( + name='address', + table='lettings_address', + ), + migrations.AlterModelTable( + name='letting', + table='lettings_letting', + ), + ] diff --git a/lettings/models.py b/lettings/models.py index 183ef6ab27..be3dc24876 100644 --- a/lettings/models.py +++ b/lettings/models.py @@ -14,7 +14,7 @@ def __str__(self): return f"{self.number} {self.street}" class Meta: - db_table = "oc_lettings_site_address" + db_table = "lettings_address" class Letting(models.Model): @@ -26,4 +26,4 @@ def __str__(self): return self.title class Meta: - db_table = "oc_lettings_site_letting" + db_table = "lettings_letting" diff --git a/oc-lettings-site.sqlite3 b/oc-lettings-site.sqlite3 index d9a0ea579cb23506899af06d0d0f5ca440149ffc..533a920216ed2f75ae2d3a31c60b114e589a0f15 100644 GIT binary patch delta 1260 zcmZ8fO>7fK6yC?$UQc#1&w!~d(j@DkAZ%yt@lQ-*NTUF5C?G*7m7qcmZsL-t#7MS* z6CxW_sVWj}x+sgd6jhZfdMQ>8>7feZ(q4lUF8qW84N~a|320TRKkLm-nr5YaZ@+Ke z``$M@GkKFt-XveNY}_YFQm6fj=iqc#2KLoYeS>nCyf&49?rF-A*L7o_u(!fqsdd-D znNqDbUOsIaTerNQ;+Zs0=X9RddAFA0d7jOuvx#&zyEB{F6|(EQMSYcXo*piaj#f(5 z>aZ)arkVHVyLWX`UD1Z=59^3E0^D-?4^WH@h7~6GdP7+Ea864U>g$l zvTiK0*kN(9;b!6@mRgI$pf80 z&ySZbi2uNY9*6=RE!hJ~f=sTEBV)z#=~XhZ(F5Cn+OtHN?#QW*oNJ-W=~P`gmyEBF zBV+B3p3BA@J+-0dGSS(mCnHsvvt?6R7z!XFL7Dx)_>W$`4)f2SR z99JJs8(&)sr(oy+(H z#|HKsDrg;IdG1mX#)zkROsY&9WnX{Z7kXE>QWFr4iW2dUhg0jBj{ct2^0^;AhTAa6 zrX{W6a;mLYaq=50`EtMO+Gk_iwTss)3T%Bf0il3E!hsbSgl+K_vg9x>57{JGfVZPMul@mY`Kh+8~;u zR(S~`O2P#*4WQd#Tbx?+OVASUa(e!E(e@V0TI1qru9fX^r(3nIKLO3ZJ?5f2HtNu# Uj@a6()@ZjoMfaTl0sL|9U;USDg#Z8m delta 507 zcmZozz}c{XbAq&>0|NttDiFhf=R_T2Mu&|FOZb_^xQZvU3+QfawCCDfEY;5{afN}C zUx9)DCI2k`BL4mS8~D5VL-@`36*e~hbWMd;U|73alrxINJat!>B`H%6hKu|)#iWSBdq|My~4XY;pW6Hm5<7z~Vt&_repE{^H!7Z`yd Nv4L|t$1TPmr2ymKpAP^4 diff --git a/profiles/migrations/0002_auto_20260205_1014.py b/profiles/migrations/0002_auto_20260205_1014.py new file mode 100644 index 0000000000..2796c38fde --- /dev/null +++ b/profiles/migrations/0002_auto_20260205_1014.py @@ -0,0 +1,47 @@ +from django.db import migrations, models +from django.conf import settings +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("profiles", "0001_initial"), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.CreateModel( + name="Profile", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="new_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "favorite_city", + models.CharField(max_length=64, blank=True), + ), + ], + options={ + "db_table": "oc_lettings_site_profile", + }, + ), + ], + ), + ] diff --git a/profiles/migrations/0003_auto_20260205_1036.py b/profiles/migrations/0003_auto_20260205_1036.py new file mode 100644 index 0000000000..b24173737a --- /dev/null +++ b/profiles/migrations/0003_auto_20260205_1036.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0 on 2026-02-05 09:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0002_auto_20260205_1014'), + ] + + operations = [ + migrations.AlterModelTable( + name='Profile', + table='profiles_profile', + ), + ] From 11840b9bddc263f7e070d703cbae9ee8533c3ee0 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 20 Feb 2026 12:22:29 +0100 Subject: [PATCH 015/113] Changed urls calls to fit with name spaces --- {templates => global_templates}/base.html | 4 ++-- {templates => global_templates}/index.html | 4 ++-- .../templates/lettings/index.html | 4 ++-- {templates => lettings/templates/lettings}/letting.html | 4 ++-- .../templates/profiles/index.html | 4 ++-- {templates => profiles/templates/profiles}/profile.html | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) rename {templates => global_templates}/base.html (96%) rename {templates => global_templates}/index.html (90%) rename templates/lettings_index.html => lettings/templates/lettings/index.html (89%) rename {templates => lettings/templates/lettings}/letting.html (94%) rename templates/profiles_index.html => profiles/templates/profiles/index.html (88%) rename {templates => profiles/templates/profiles}/profile.html (94%) diff --git a/templates/base.html b/global_templates/base.html similarity index 96% rename from templates/base.html rename to global_templates/base.html index ab7addba01..51c8b727be 100644 --- a/templates/base.html +++ b/global_templates/base.html @@ -24,10 +24,10 @@
Logo Orange County Lettings diff --git a/templates/index.html b/global_templates/index.html similarity index 90% rename from templates/index.html rename to global_templates/index.html index 71a8e61a46..40e5dd5911 100644 --- a/templates/index.html +++ b/global_templates/index.html @@ -14,10 +14,10 @@

Welcome to Holiday Homes

diff --git a/templates/lettings_index.html b/lettings/templates/lettings/index.html similarity index 89% rename from templates/lettings_index.html rename to lettings/templates/lettings/index.html index 92857a78d9..ee6db103ab 100644 --- a/templates/lettings_index.html +++ b/lettings/templates/lettings/index.html @@ -20,7 +20,7 @@

Lettings

@@ -36,7 +36,7 @@

Lettings

Home - + Profiles
diff --git a/templates/letting.html b/lettings/templates/lettings/letting.html similarity index 94% rename from templates/letting.html rename to lettings/templates/lettings/letting.html index 7e5f3a73fd..cbaf2690d5 100644 --- a/templates/letting.html +++ b/lettings/templates/lettings/letting.html @@ -25,14 +25,14 @@

{{ title }}

diff --git a/templates/profiles_index.html b/profiles/templates/profiles/index.html similarity index 88% rename from templates/profiles_index.html rename to profiles/templates/profiles/index.html index 4ad1daf92f..76baf754b0 100644 --- a/templates/profiles_index.html +++ b/profiles/templates/profiles/index.html @@ -18,7 +18,7 @@

Profiles

@@ -34,7 +34,7 @@

Profiles

Home - + Lettings
diff --git a/templates/profile.html b/profiles/templates/profiles/profile.html similarity index 94% rename from templates/profile.html rename to profiles/templates/profiles/profile.html index d150d30e63..a5558e0da0 100644 --- a/templates/profile.html +++ b/profiles/templates/profiles/profile.html @@ -24,14 +24,14 @@

{{ profile.user.username }}

From 6da4ff7673839f3b6beb3921f8fd5bb703f65a66 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 20 Feb 2026 12:22:58 +0100 Subject: [PATCH 016/113] Updated urls post migration --- lettings/urls.py | 9 +++++++++ oc_lettings_site/urls.py | 8 +++----- profiles/urls.py | 9 +++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 lettings/urls.py create mode 100644 profiles/urls.py diff --git a/lettings/urls.py b/lettings/urls.py new file mode 100644 index 0000000000..bf686e0b64 --- /dev/null +++ b/lettings/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = "lettings" +urlpatterns = [ + path("", views.lettings_index, name="lettings_index"), + path("/", views.letting, name="letting"), +] diff --git a/oc_lettings_site/urls.py b/oc_lettings_site/urls.py index f0ff5897ab..83498f45f2 100644 --- a/oc_lettings_site/urls.py +++ b/oc_lettings_site/urls.py @@ -1,13 +1,11 @@ from django.contrib import admin -from django.urls import path +from django.urls import path, include from . import views urlpatterns = [ path('', views.index, name='index'), - path('lettings/', views.lettings_index, name='lettings_index'), - path('lettings//', views.letting, name='letting'), - path('profiles/', views.profiles_index, name='profiles_index'), - path('profiles//', views.profile, name='profile'), + path('lettings/', include('lettings.urls')), + path('profiles/', include('profiles.urls')), path('admin/', admin.site.urls), ] diff --git a/profiles/urls.py b/profiles/urls.py new file mode 100644 index 0000000000..7e1291d198 --- /dev/null +++ b/profiles/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from . import views + +app_name = "profiles" + +urlpatterns = [ + path("", views.profiles_index, name="profiles_index"), + path("/", views.profile, name="profile"), +] From 3eb9810b9dd128a3fb74f5ced189de86a564e7d3 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 20 Feb 2026 12:23:41 +0100 Subject: [PATCH 017/113] Created views, and transfered functions properly --- lettings/views.py | 16 ++++++++++++- oc_lettings_site/views.py | 48 --------------------------------------- profiles/views.py | 13 ++++++++++- 3 files changed, 27 insertions(+), 50 deletions(-) diff --git a/lettings/views.py b/lettings/views.py index 91ea44a218..f6308fd494 100644 --- a/lettings/views.py +++ b/lettings/views.py @@ -1,3 +1,17 @@ from django.shortcuts import render +from .models import Letting -# Create your views here. + +def lettings_index(request): + lettings_list = Letting.objects.all() + context = {"lettings_list": lettings_list} + return render(request, "lettings/index.html", context) + + +def letting(request, letting_id): + letting = Letting.objects.get(id=letting_id) + context = { + "title": letting.title, + "address": letting.address, + } + return render(request, "lettings/letting.html", context) diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index 02e9c329a8..f592a8beba 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -14,53 +14,5 @@ def index(request): return render(request, "index.html") -# Aenean leo magna, vestibulum et tincidunt fermentum, consectetur quis velit. -# Sed non placerat massa. Integer est nunc, pulvinar a -# tempor et, bibendum id arcu. Vestibulum ante ipsum primis in faucibus orci luctus et -# ultrices posuere cubilia curae; Cras eget scelerisque -def lettings_index(request): - lettings_list = Letting.objects.all() - context = {"lettings_list": lettings_list} - return render(request, "lettings_index.html", context) -# Cras ultricies dignissim purus, vitae hendrerit ex varius non. -# In accumsan porta nisl id eleifend. -# Praesent dignissim, odio eu consequat pretium, purus urna vulputate arcu, vitae efficitur -# lacus justo nec purus. Aenean finibus faucibus lectus at porta. -# Maecenas auctor, est ut luctus congue, dui enim mattis enim, ac condimentum velit libero -# in magna. -# Suspendisse potenti. In tempus a nisi sed laoreet. -# Suspendisse porta dui eget sem accumsan interdum. Ut quis urna pellentesque justo mattis -# ullamcorper ac non tellus. -# In tristique mauris eu velit fermentum, tempus pharetra est luctus. -# Vivamus consequat aliquam libero, eget bibendum lorem. Sed non dolor risus. -# Mauris condimentum auctor elementum. Donec quis nisi ligula. Integer vehicula tincidunt enim, -# ac lacinia augue pulvinar sit amet. -def letting(request, letting_id): - letting = Letting.objects.get(id=letting_id) - context = { - "title": letting.title, - "address": letting.address, - } - return render(request, "letting.html", context) - - -# Sed placerat quam in pulvinar commodo. Nullam laoreet consectetur ex, sed consequat libero -# pulvinar eget. Fusc -# faucibus, urna quis auctor pharetra, massa dolor cursus neque, quis dictum lacus d -def profiles_index(request): - profiles_list = Profile.objects.all() - context = {"profiles_list": profiles_list} - return render(request, "profiles_index.html", context) - - -# Aliquam sed metus eget nisi tincidunt ornare accumsan eget lac -# laoreet neque quis, pellentesque dui. Nullam facilisis pharetra vulputate. -# Sed tincidunt, dolor id facilisis fringilla, eros leo tristique lacus, -# it. Nam aliquam dignissim congue. Pellentesque habitant morbi tristique senectus et netus -# et males -def profile(request, username): - profile = Profile.objects.get(user__username=username) - context = {"profile": profile} - return render(request, "profile.html", context) diff --git a/profiles/views.py b/profiles/views.py index 91ea44a218..01eb46f3f6 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -1,3 +1,14 @@ from django.shortcuts import render +from .models import Profile -# Create your views here. + +def profiles_index(request): + profiles_list = Profile.objects.all() + context = {"profiles_list": profiles_list} + return render(request, "profiles/index.html", context) + + +def profile(request, username): + profile = Profile.objects.get(user__username=username) + context = {"profile": profile} + return render(request, "profiles/profile.html", context) From d1af5e2c11073c212855d8e1f1478d5eaa3cbf41 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 20 Feb 2026 12:24:10 +0100 Subject: [PATCH 018/113] Deleted unused files --- oc_lettings_site/admin.py | 0 oc_lettings_site/models.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 oc_lettings_site/admin.py delete mode 100644 oc_lettings_site/models.py diff --git a/oc_lettings_site/admin.py b/oc_lettings_site/admin.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/oc_lettings_site/models.py b/oc_lettings_site/models.py deleted file mode 100644 index e69de29bb2..0000000000 From 67117421f2e09f64736950cb08be8df9a980673e Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 20 Feb 2026 12:24:38 +0100 Subject: [PATCH 019/113] last migration and update for architecture refactoring --- lettings/migrations/0002_address_letting.py | 65 ++++++++++++++++----- oc_lettings_site/settings.py | 2 +- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/lettings/migrations/0002_address_letting.py b/lettings/migrations/0002_address_letting.py index 46fcc82e76..588784c641 100644 --- a/lettings/migrations/0002_address_letting.py +++ b/lettings/migrations/0002_address_letting.py @@ -10,34 +10,69 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('lettings', '0001_initial'), + ("lettings", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Address', + name="Address", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('number', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(9999)])), - ('street', models.CharField(max_length=64)), - ('city', models.CharField(max_length=64)), - ('state', models.CharField(max_length=2, validators=[django.core.validators.MinLengthValidator(2)])), - ('zip_code', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(99999)])), - ('country_iso_code', models.CharField(max_length=3, validators=[django.core.validators.MinLengthValidator(3)])), + ( + "id", + models.AutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ( + "number", + models.PositiveIntegerField( + validators=[django.core.validators.MaxValueValidator(9999)] + ), + ), + ("street", models.CharField(max_length=64)), + ("city", models.CharField(max_length=64)), + ( + "state", + models.CharField( + max_length=2, validators=[django.core.validators.MinLengthValidator(2)] + ), + ), + ( + "zip_code", + models.PositiveIntegerField( + validators=[django.core.validators.MaxValueValidator(99999)] + ), + ), + ( + "country_iso_code", + models.CharField( + max_length=3, validators=[django.core.validators.MinLengthValidator(3)] + ), + ), ], options={ - 'db_table': 'oc_lettings_site_address', + "db_table": "oc_lettings_site_address", }, ), migrations.CreateModel( - name='Letting', + name="Letting", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=256)), - ('address', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='lettings.Address')), + ( + "id", + models.AutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ("title", models.CharField(max_length=256)), + ( + "address", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, to="lettings.Address" + ), + ), ], options={ - 'db_table': 'oc_lettings_site_letting', + "db_table": "oc_lettings_site_letting", }, ), ] diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 3f4fd60f00..778bd8fd1d 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -45,7 +45,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(BASE_DIR, "templates")], + "DIRS": [os.path.join(BASE_DIR, "global_templates")], "APP_DIRS": True, "OPTIONS": { "context_processors": [ From 6a41ba1991e5e664c87f1aba52371156befcabda Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 20 Feb 2026 18:10:24 +0100 Subject: [PATCH 020/113] Deleted unused imports --- oc_lettings_site/views.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index f592a8beba..8d19502083 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -1,6 +1,4 @@ from django.shortcuts import render -from lettings.models import Letting -from profiles.models import Profile # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque molestie quam lobortis leo @@ -12,7 +10,3 @@ # enim cursus in. Praesent volutpat porttitor magna, non finibus neque cursus id. def index(request): return render(request, "index.html") - - - - From 100a9f2f1e391163543da5c96c1699f3685d1483 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 22 Feb 2026 14:59:52 +0100 Subject: [PATCH 021/113] Updated requirements.txt --- requirements.txt | Bin 46 -> 910 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index f6acb73f854deda54e31189faa52af866ea5fc9a..7a09e98b31e9b9ffee14141c66af18899544fb1e 100644 GIT binary patch literal 910 zcmZ`%+fIW}5S(WdKgFi>W_|F@5AY8RML=vRSWXl0l`+1c6i{gt9a zi3%6wDDcQTyHRF4pyMk&nG(;P!r>OZ#e@VuM z@6Obp@XqryJv4@@*8Ao>4Xhl{607WAH!G~II!DhQIT?YB#&j$K$%MAB=xl+Qd%@pWZDc5*qzYF Date: Wed, 25 Feb 2026 10:37:01 +0100 Subject: [PATCH 022/113] Added documentation for lettings files. --- lettings/admin.py | 33 +++++++++++++++++++++-- lettings/apps.py | 18 ++++++++++++- lettings/models.py | 66 ++++++++++++++++++++++++++++++++++++++++++++-- lettings/urls.py | 15 +++++++++++ lettings/views.py | 41 ++++++++++++++++++++++++++-- 5 files changed, 166 insertions(+), 7 deletions(-) diff --git a/lettings/admin.py b/lettings/admin.py index 3b3fb925bd..5cf0fd0168 100644 --- a/lettings/admin.py +++ b/lettings/admin.py @@ -1,8 +1,37 @@ +""" +Admin configuration for the lettings application. + +This module registers the Letting and Address models +with the Django admin interface, allowing administrators +to manage lettings and related address data through +the built-in Django admin site. +""" + from django.contrib import admin from .models import Letting from .models import Address -admin.site.register(Letting) -admin.site.register(Address) +@admin.register(Letting) +class LettingAdmin(admin.ModelAdmin): + """ + Admin configuration for the Letting model. + + This class enables the management of Letting instances + within the Django admin interface. + """ + + pass + + +@admin.register(Address) +class AddressAdmin(admin.ModelAdmin): + """ + Admin configuration for the Address model. + + This class enables the management of Address instances + within the Django admin interface. + """ + + pass diff --git a/lettings/apps.py b/lettings/apps.py index b6abff1791..8eeb0d1cde 100644 --- a/lettings/apps.py +++ b/lettings/apps.py @@ -1,5 +1,21 @@ +""" +Application configuration for the lettings app. + +This module defines the configuration class used by Django +to register and initialize the lettings application within +the project. +""" + from django.apps import AppConfig class LettingsConfig(AppConfig): - name = 'lettings' + """ + Configuration class for the lettings application. + + This class specifies the application name and allows + future customization of application-level behavior + such as signal registration or startup logic. + """ + + name = "lettings" diff --git a/lettings/models.py b/lettings/models.py index be3dc24876..8371f84bd9 100644 --- a/lettings/models.py +++ b/lettings/models.py @@ -1,8 +1,39 @@ +""" +Database models for the lettings application. + +This module defines the core data structures used to represent +rental properties and their associated addresses. + +Two main models are provided: + +- Address: Represents a physical location with validation constraints. +- Letting: Represents a rental unit associated with a unique address. + +These models are mapped to dedicated database tables and enforce +basic validation rules at the model level. +""" + from django.db import models from django.core.validators import MaxValueValidator, MinLengthValidator class Address(models.Model): + """ + Represents a physical address associated with a letting. + + The Address model stores structured location data and enforces + validation rules on numerical and string-based fields to ensure + consistency of stored information. + + Attributes: + number (PositiveIntegerField): Street number (maximum 4 digits). + street (CharField): Street name (maximum 64 characters). + city (CharField): City name (maximum 64 characters). + state (CharField): Two-character state code (minimum length enforced). + zip_code (PositiveIntegerField): Postal code (maximum 5 digits). + country_iso_code (CharField): ISO country code (3 characters minimum). + """ + number = models.PositiveIntegerField(validators=[MaxValueValidator(9999)]) street = models.CharField(max_length=64) city = models.CharField(max_length=64) @@ -10,20 +41,51 @@ class Address(models.Model): zip_code = models.PositiveIntegerField(validators=[MaxValueValidator(99999)]) country_iso_code = models.CharField(max_length=3, validators=[MinLengthValidator(3)]) - def __str__(self): + def __str__(self) -> str: + """ + Return a human-readable representation of the address. + + Returns: + str: A formatted string combining street number and street name. + """ return f"{self.number} {self.street}" class Meta: + """ + Metadata configuration for the Address model. + """ + db_table = "lettings_address" class Letting(models.Model): + """ + Represents a rental property. + + The Letting model links a rental unit to a unique Address instance + using a one-to-one relationship. Each letting corresponds to exactly + one physical address. + + Attributes: + title (CharField): Name or title of the letting (maximum 256 characters). + address (OneToOneField): Unique associated Address instance. + """ + title = models.CharField(max_length=256) address = models.OneToOneField(Address, on_delete=models.CASCADE) - related_name = "new_letting" def __str__(self): + """ + Return a human-readable representation of the letting. + + Returns: + str: The lettings title. + """ return self.title class Meta: + """ + Metadata configuration for the Letting model. + """ + db_table = "lettings_letting" diff --git a/lettings/urls.py b/lettings/urls.py index bf686e0b64..4bc6ba961a 100644 --- a/lettings/urls.py +++ b/lettings/urls.py @@ -1,9 +1,24 @@ +""" +URL configuration for the lettings application. + +This module defines the URL patterns associated with the lettings app. +It maps URL paths to their corresponding view functions responsible +for rendering letting listings and individual letting details. + +The `app_name` variable enables namespaced URL resolution within +the Django project. +""" + from django.urls import path from . import views app_name = "lettings" urlpatterns = [ + # Root path: displays the list of all available lettings path("", views.lettings_index, name="lettings_index"), + + # Dynamic route: displays details for a specific letting + # identified by its primary key (letting_id) path("/", views.letting, name="letting"), ] diff --git a/lettings/views.py b/lettings/views.py index f6308fd494..bb42ce8715 100644 --- a/lettings/views.py +++ b/lettings/views.py @@ -1,14 +1,51 @@ +""" +Views for the lettings application. + +This module defines view functions responsible for displaying +the list of available lettings and the details of a specific letting. + +Each view retrieves data from the database using the Letting model +and renders the appropriate HTML template with a context dictionary. +""" + from django.shortcuts import render +from django.http import HttpRequest, HttpResponse from .models import Letting -def lettings_index(request): +def lettings_index(request: HttpRequest) -> HttpResponse: + """ + Display the list of all available lettings. + + This view retrieves all Letting instances from the database + and renders them using the ``lettings/index.html`` template. + + :param request: The HTTP request object. + :type request: HttpRequest + :return: Rendered HTML page displaying the list of lettings. + :rtype: HttpResponse + """ lettings_list = Letting.objects.all() context = {"lettings_list": lettings_list} return render(request, "lettings/index.html", context) -def letting(request, letting_id): +def letting(request: HttpRequest, letting_id: int) -> HttpResponse: + """ + Display the details of a specific letting. + + This view retrieves a single Letting instance based on its + primary key and renders its details using the + ``lettings/letting.html`` template. + + :param request: The HTTP request object. + :type request: HttpRequest + :param letting_id: The unique identifier of the letting. + :type letting_id: int + :return: Rendered HTML page displaying the letting details. + :rtype: HttpResponse + :raises Letting.DoesNotExist: If no letting matches the given ID. + """ letting = Letting.objects.get(id=letting_id) context = { "title": letting.title, From 51b86dfc3434fde053173f8feaf9c6fffb24c28b Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 25 Feb 2026 11:33:41 +0100 Subject: [PATCH 023/113] Added documentation on profiles app code --- profiles/admin.py | 20 ++++++++++++++++++-- profiles/apps.py | 14 ++++++++++++++ profiles/models.py | 31 +++++++++++++++++++++++++++++++ profiles/urls.py | 15 +++++++++++++++ profiles/views.py | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 2 deletions(-) diff --git a/profiles/admin.py b/profiles/admin.py index f045416366..321e28ba87 100644 --- a/profiles/admin.py +++ b/profiles/admin.py @@ -1,6 +1,22 @@ -from django.contrib import admin +""" +Admin configuration for the profiles application. + +This module registers the Profile model with the Django admin interface, +allowing administrators to manage user profile data through +the built-in Django admin site. +""" +from django.contrib import admin from .models import Profile -admin.site.register(Profile) +@admin.register(Profile) +class ProfileAdmin(admin.ModelAdmin): + """ + Admin configuration for the Profile model. + + This class enables the management of Profile instances + within the Django admin interface. + """ + + pass diff --git a/profiles/apps.py b/profiles/apps.py index 5501fdad35..5f62818252 100644 --- a/profiles/apps.py +++ b/profiles/apps.py @@ -1,5 +1,19 @@ +""" +App configuration for the profiles application. + +This module defines the ProfilesConfig class, which provides +Django with metadata and configuration for the profiles app. +""" + from django.apps import AppConfig class ProfilesConfig(AppConfig): + """ + Configuration class for the profiles app. + + The `name` attribute specifies the full Python path to + the application, enabling Django to correctly register + and load the app within the project. + """ name = 'profiles' diff --git a/profiles/models.py b/profiles/models.py index e2ca495454..265a146457 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -1,13 +1,44 @@ +""" +Models for the profiles application. + +This module defines the Profile model, which extends the built-in +Django User model with additional application-specific data, +such as the user's favorite city. +""" + from django.db import models from django.contrib.auth.models import User class Profile(models.Model): + """ + Profile model extending Django's built-in User. + + Attributes: + user (OneToOneField): A one-to-one relationship with the User model, + ensuring each user has a single profile. The related_name + "new_user" allows reverse access from User instances. + favorite_city (CharField): Optional field storing the user's + favorite city, with a maximum length of 64 characters. + """ + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="new_user") favorite_city = models.CharField(max_length=64, blank=True) def __str__(self): + """ + Returns a string representation of the Profile instance. + + Returns: + str: The username of the associated User. + """ return self.user.username class Meta: + """ + Metadata for the Profile model. + + Attributes: + db_table (str): Explicit database table name for the model. + """ db_table = "profiles_profile" diff --git a/profiles/urls.py b/profiles/urls.py index 7e1291d198..2efde1d451 100644 --- a/profiles/urls.py +++ b/profiles/urls.py @@ -1,9 +1,24 @@ +""" +URL configuration for the profiles application. + +This module defines the URL patterns associated with the profiles app. +It maps URL paths to their corresponding view functions responsible +for rendering profile listings and individual profile details. + +The `app_name` variable enables namespaced URL resolution within +the Django project. +""" + from django.urls import path from . import views app_name = "profiles" urlpatterns = [ + # Root path: displays the list of all available profiles path("", views.profiles_index, name="profiles_index"), + + # Dynamic route: displays details for a specific profile + # identified by the username path("/", views.profile, name="profile"), ] diff --git a/profiles/views.py b/profiles/views.py index 01eb46f3f6..1fced94d7a 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -1,14 +1,50 @@ +""" +Views module for the profiles application. + +This module contains the view functions responsible for rendering +the profiles list and individual profile detail pages. +Each view retrieves data from the database and passes it to +the corresponding templates for rendering. +""" + from django.shortcuts import render from .models import Profile def profiles_index(request): + """ + Render a page displaying all profiles. + + Retrieves all Profile instances from the database and passes them + to the 'profiles/index.html' template under the context variable + 'profiles_list'. + + Args: + request (HttpRequest): The HTTP request object. + + Returns: + HttpResponse: Rendered HTML page with the list of profiles. + """ profiles_list = Profile.objects.all() context = {"profiles_list": profiles_list} return render(request, "profiles/index.html", context) def profile(request, username): + """ + Render a page displaying details of a specific profile. + + Retrieves a Profile instance from the database corresponding + to the provided username and passes it to the + 'profiles/profile.html' template under the context variable 'profile'. + + Args: + request (HttpRequest): The HTTP request object. + username (str): The username of the user whose profile is requested. + + Returns: + HttpResponse: Rendered HTML page with the profile details. + """ profile = Profile.objects.get(user__username=username) context = {"profile": profile} return render(request, "profiles/profile.html", context) From 6330725022957917069d9baf72d6647204452b01 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 25 Feb 2026 11:34:16 +0100 Subject: [PATCH 024/113] Added documentation on og_lettings_site files, and manage.py --- manage.py | 21 +++++++++++++++++++++ oc_lettings_site/asgi.py | 15 ++++++++++++++- oc_lettings_site/settings.py | 20 ++++++++++++++++++++ oc_lettings_site/urls.py | 21 +++++++++++++++++++++ oc_lettings_site/views.py | 26 +++++++++++++++++++------- oc_lettings_site/wsgi.py | 14 +++++++++++++- 6 files changed, 108 insertions(+), 9 deletions(-) diff --git a/manage.py b/manage.py index c0e27e034a..14f26f0945 100755 --- a/manage.py +++ b/manage.py @@ -1,8 +1,29 @@ +""" +Management script for the Django project. + +This module serves as the command-line utility for administrative tasks +in the Django project, such as running the development server, +applying migrations, creating superusers, and executing custom management commands. + +It sets the default settings module for the Django project and +delegates execution to Django's built-in command-line utility. +""" + import os import sys def main(): + """ + Entrypoint for the Django management script. + + Ensures that the DJANGO_SETTINGS_MODULE environment variable is set + to the project's settings module and invokes Django's + execute_from_command_line function to handle command-line arguments. + + Raises: + ImportError: If Django is not installed or cannot be imported. + """ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oc_lettings_site.settings') try: from django.core.management import execute_from_command_line diff --git a/oc_lettings_site/asgi.py b/oc_lettings_site/asgi.py index 61f2d23ba3..2768640dfb 100644 --- a/oc_lettings_site/asgi.py +++ b/oc_lettings_site/asgi.py @@ -1,7 +1,20 @@ +""" +ASGI configuration for the oc_lettings_site project. + +This module exposes the ASGI callable as a module-level variable +named ``application``. + +It is used by ASGI-compatible web servers to serve the Django +application and enables support for asynchronous features +such as WebSockets or long-lived connections. +""" + import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oc_lettings_site.settings') +# Set the default Django settings module for the ASGI application +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oc_lettings_site.settings") +# ASGI application callable used by compatible servers application = get_asgi_application() diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 778bd8fd1d..8a10343108 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -1,3 +1,23 @@ +""" +Django settings for the oc_lettings_site project. + +This configuration file defines the core settings used to run +the application in a development environment. + +It includes: + +- Application registration +- Middleware configuration +- Template settings +- Database configuration (SQLite) +- Authentication and password validation rules +- Internationalization settings +- Static file management + +These settings are intended for development purposes and +are not optimized for production deployment. +""" + import os from pathlib import Path diff --git a/oc_lettings_site/urls.py b/oc_lettings_site/urls.py index 83498f45f2..b3c41bba76 100644 --- a/oc_lettings_site/urls.py +++ b/oc_lettings_site/urls.py @@ -1,11 +1,32 @@ +""" +URL configuration for the main Django project (oc_lettings_site). + +This module defines the root URL patterns for the project, including: + +- The home page (`index`) +- Inclusion of the lettings app URLs +- Inclusion of the profiles app URLs +- Django admin interface + +It maps URL paths to the corresponding view functions or included +URLconfs. Namespacing is managed at the app level where necessary. +""" + from django.contrib import admin from django.urls import path, include from . import views urlpatterns = [ + # Home page path('', views.index, name='index'), + + # Lettings app: routes are defined in lettings.urls path('lettings/', include('lettings.urls')), + + # Profiles app: routes are defined in profiles.urls path('profiles/', include('profiles.urls')), + + # Django admin interface path('admin/', admin.site.urls), ] diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index 8d19502083..cd324ed64c 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -1,12 +1,24 @@ +""" +Views for the main Django project (oc_lettings_site). + +This module contains view functions responsible for rendering +the templates of the project's main pages, such as the home page. +""" + from django.shortcuts import render -# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque molestie quam lobortis leo -# consectetur ullamcorper non id est. Praesent dictum, nulla eget feugiat sagittis, sem mi -# convallis eros, -# vitae dapibus nisi lorem dapibus sem. Maecenas pharetra purus ipsum, eget consequat ipsum -# lobortis quis. Phasellus eleifend ex auctor venenatis tempus. -# Aliquam vitae erat ac orci placerat luctus. Nullam elementum urna nisi, pellentesque iaculis -# enim cursus in. Praesent volutpat porttitor magna, non finibus neque cursus id. def index(request): + """ + Render the home page of the Orange County Lettings site. + + This view function handles requests to the root URL ('/'). + It returns a response using the 'index.html' template. + + Args: + request (HttpRequest): The HTTP request object. + + Returns: + HttpResponse: Rendered home page template. + """ return render(request, "index.html") diff --git a/oc_lettings_site/wsgi.py b/oc_lettings_site/wsgi.py index d78ca6d669..4aed681638 100644 --- a/oc_lettings_site/wsgi.py +++ b/oc_lettings_site/wsgi.py @@ -1,7 +1,19 @@ -import os +""" +WSGI config for oc_lettings_site project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It exposes the WSGI callable as a module-level +variable named `application`. +WSGI (Web Server Gateway Interface) is a specification that describes how a web server +communicates with web applications. +""" + +import os from django.core.wsgi import get_wsgi_application +# Set the default settings module for the 'oc_lettings_site' project. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oc_lettings_site.settings') +# WSGI application callable for serving the project. application = get_wsgi_application() From d49e29ef3a02ef4a7b33546f7c849177995c832d Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 25 Feb 2026 11:35:36 +0100 Subject: [PATCH 025/113] Added documentation on og_lettings_site files, and manage.py --- oc_lettings_site/apps.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/oc_lettings_site/apps.py b/oc_lettings_site/apps.py index 6489692f04..456a5d5786 100644 --- a/oc_lettings_site/apps.py +++ b/oc_lettings_site/apps.py @@ -1,5 +1,23 @@ +""" +Application configuration for the oc_lettings_site project. + +This module defines the configuration class used by Django +to initialize and register the main project application. + +Although minimal, this configuration class serves as the +entry point for application-level customization, such as +signal registration or startup logic. +""" + from django.apps import AppConfig class OCLettingsSiteConfig(AppConfig): - name = 'oc_lettings_site' + """ + Configuration class for the oc_lettings_site application. + + This class declares the application name and provides a + foundation for future project-wide initialization behavior. + """ + + name = "oc_lettings_site" From f65ad1d83087abd377b81f7cd5de79b32bb991de Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 25 Feb 2026 11:56:26 +0100 Subject: [PATCH 026/113] Created a conftest file for tests in tellings app --- lettings/conftest.py | 69 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 lettings/conftest.py diff --git a/lettings/conftest.py b/lettings/conftest.py new file mode 100644 index 0000000000..3199fdac82 --- /dev/null +++ b/lettings/conftest.py @@ -0,0 +1,69 @@ +# conftest.py +""" +Central test configuration for the Django OC Lettings project. + +This module defines reusable pytest fixtures for testing the lettings +application. Fixtures provide pre-created instances of models such as +Address and Letting, which can be injected into unit and integration +tests to ensure consistent and isolated test environments. + +Fixtures: + - address: Creates a sample Address instance. + - letting: Creates a sample Letting instance linked to an Address. +""" + +import pytest +from lettings.models import Address, Letting + + +@pytest.fixture +def address(): + """ + Fixture that creates a sample Address instance. + + The Address instance represents a physical location with + predefined fields suitable for testing purposes. The object + is saved in the test database and is available for injection + into test functions. + + Returns: + Address: A saved Address instance with sample data. + + Example usage in a test function: + def test_address_str(address): + assert str(address) == "123 Main Street" + """ + return Address.objects.create( + number=123, + street="Main Street", + city="Los Angeles", + state="CA", + zip_code=90001, + country_iso_code="USA" + ) + + +@pytest.fixture +def letting(address): + """ + Fixture that creates a sample Letting instance linked to an Address. + + The Letting instance represents a rental property associated + with the provided Address fixture. It is saved in the test + database and can be injected into test functions to test + model behavior, view responses, and URL routing. + + Args: + address (Address): A fixture providing an Address instance. + + Returns: + Letting: A saved Letting instance linked to the provided address. + + Example usage in a test function: + def test_letting_str(letting): + assert str(letting) == "Beautiful Apartment" + """ + return Letting.objects.create( + title="Beautiful Apartment", + address=address + ) From 32439432cc5c15b5dd27ad527bae570e73895cee Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 25 Feb 2026 12:11:52 +0100 Subject: [PATCH 027/113] Created a few unittests for lettings.models --- lettings/tests.py | 88 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/lettings/tests.py b/lettings/tests.py index 7ce503c2dd..30bb3814ee 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -1,3 +1,87 @@ -from django.test import TestCase +# tests/test_models.py +""" +Unit tests for the lettings.models module. -# Create your tests here. +This file contains tests for the Address and Letting models, +ensuring that model fields, string representations, and +basic validations work correctly. + +Fixtures: + - address: Pre-created Address instance for tests. +""" + +import pytest +from lettings.models import Address + + +@pytest.mark.django_db +def test_address_str(address): + """ + Test the string representation of the Address model. + + The __str__ method should return a formatted string combining + the street number and the street name. + + Args: + address (Address): Fixture providing a sample Address instance. + """ + assert str(address) == "123 Main Street" + + +@pytest.mark.django_db +def test_address_fields(address): + """ + Test that the Address fields are correctly set from the fixture. + + Args: + address (Address): Fixture providing a sample Address instance. + """ + assert address.number == 123 + assert address.street == "Main Street" + assert address.city == "Los Angeles" + assert address.state == "CA" + assert address.zip_code == 90001 + assert address.country_iso_code == "USA" + + +# tests/test_models.py +""" +Unit tests for the Letting model in lettings.models. + +This file contains tests for the Address and Letting models, +ensuring that model fields, string representations, and +one-to-one relationships work correctly. +""" + +import pytest +from lettings.models import Letting, Address + + +@pytest.mark.django_db +def test_letting_str(letting): + """ + Test the string representation of the Letting model. + + The __str__ method should return the title of the letting. + + Args: + letting (Letting): Fixture providing a sample Letting instance. + """ + assert str(letting) == "Beautiful Apartment" + + +@pytest.mark.django_db +def test_letting_fields(letting, address): + """ + Test that the Letting fields are correctly set and linked to Address. + + Args: + letting (Letting): Fixture providing a sample Letting instance. + address (Address): Fixture providing a sample Address instance. + """ + assert letting.title == "Beautiful Apartment" + # Check the one-to-one relationship + assert letting.address == address + # Confirm related data + assert letting.address.street == "Main Street" + assert letting.address.city == "Los Angeles" From 7ba9288f1356b3dfb955b06038f74207d6b42e8c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Thu, 26 Feb 2026 10:33:56 +0100 Subject: [PATCH 028/113] Created a few unittests for lettings.urls --- lettings/tests.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/lettings/tests.py b/lettings/tests.py index 30bb3814ee..ad9d8634f3 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -85,3 +85,92 @@ def test_letting_fields(letting, address): # Confirm related data assert letting.address.street == "Main Street" assert letting.address.city == "Los Angeles" + + +""" +URL tests for the lettings application. + +This module verifies that URL patterns defined in lettings.urls +correctly resolve to their associated view functions and that +named routes generate the expected paths. + +The following aspects are tested: +- URL reversing using Django's reverse function +- URL resolution using Django's resolve function +- HTTP response status codes for defined routes +""" + +import pytest +from django.urls import reverse, resolve +from lettings import views + + +@pytest.mark.django_db +def test_lettings_index_url_reverse(): + """ + Test that the lettings index URL is correctly reversed. + + Ensures that the named route 'lettings:lettings_index' + generates the expected URL path. + """ + url = reverse("lettings:lettings_index") + assert url == "/lettings/" + + +@pytest.mark.django_db +def test_letting_detail_url_reverse(letting): + """ + Test that the letting detail URL is correctly reversed + with a valid letting ID. + + Args: + letting (Letting): Fixture providing a sample Letting instance. + """ + url = reverse("lettings:letting", args=[letting.id]) + assert url == f"/lettings/{letting.id}/" + + +@pytest.mark.django_db +def test_lettings_index_url_resolves(): + """ + Test that the lettings index URL resolves + to the correct view function. + """ + resolver = resolve("/lettings/") + assert resolver.func == views.lettings_index + + +@pytest.mark.django_db +def test_letting_detail_url_resolves(letting): + """ + Test that the letting detail URL resolves + to the correct view function. + + Args: + letting (Letting): Fixture providing a sample Letting instance. + """ + resolver = resolve(f"/lettings/{letting.id}/") + assert resolver.func == views.letting + + +@pytest.mark.django_db +def test_lettings_index_http_response(client): + """ + Integration test ensuring that the lettings index + URL returns an HTTP 200 response. + """ + response = client.get(reverse("lettings:lettings_index")) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_letting_detail_http_response(client, letting): + """ + Integration test ensuring that the letting detail + URL returns an HTTP 200 response for a valid ID. + + Args: + letting (Letting): Fixture providing a sample Letting instance. + """ + response = client.get(reverse("lettings:letting", args=[letting.id])) + assert response.status_code == 200 From 7d2857d406dd4f33c16d9e9d7acb45a262808fb1 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Thu, 26 Feb 2026 10:41:14 +0100 Subject: [PATCH 029/113] Created a few unittests for lettings.views, all lettings files are unit tested --- lettings/tests.py | 105 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/lettings/tests.py b/lettings/tests.py index ad9d8634f3..4422f7011c 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -11,7 +11,8 @@ """ import pytest -from lettings.models import Address +from django.urls import reverse, resolve +from lettings import views @pytest.mark.django_db @@ -100,10 +101,6 @@ def test_letting_fields(letting, address): - HTTP response status codes for defined routes """ -import pytest -from django.urls import reverse, resolve -from lettings import views - @pytest.mark.django_db def test_lettings_index_url_reverse(): @@ -174,3 +171,101 @@ def test_letting_detail_http_response(client, letting): """ response = client.get(reverse("lettings:letting", args=[letting.id])) assert response.status_code == 200 + + +""" +View tests for the lettings application. + +This module verifies that view functions defined in lettings.views +correctly render templates, return expected HTTP responses, +and provide appropriate context data. + +The following views are tested: +- lettings_index +- letting +""" + + +@pytest.mark.django_db +def test_lettings_index_view_status_code(client): + """ + Test that the lettings_index view returns HTTP 200. + """ + response = client.get(reverse("lettings:lettings_index")) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_lettings_index_view_template(client): + """ + Test that the correct template is used + for the lettings_index view. + """ + response = client.get(reverse("lettings:lettings_index")) + assert "lettings/index.html" in [t.name for t in response.templates] + + +@pytest.mark.django_db +def test_lettings_index_context(client, letting): + """ + Test that the lettings_index view provides + the correct context data. + + Args: + letting (Letting): Fixture providing a sample Letting instance. + """ + response = client.get(reverse("lettings:lettings_index")) + + assert "lettings_list" in response.context + assert letting in response.context["lettings_list"] + + +@pytest.mark.django_db +def test_letting_detail_view_status_code(client, letting): + """ + Test that the letting detail view returns HTTP 200 + for a valid letting ID. + """ + response = client.get( + reverse("lettings:letting", args=[letting.id]) + ) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_letting_detail_view_template(client, letting): + """ + Test that the correct template is used + for the letting detail view. + """ + response = client.get( + reverse("lettings:letting", args=[letting.id]) + ) + assert "lettings/letting.html" in [t.name for t in response.templates] + + +@pytest.mark.django_db +def test_letting_detail_context(client, letting): + """ + Test that the letting detail view provides + the correct context variables. + + Args: + letting (Letting): Fixture providing a sample Letting instance. + """ + response = client.get( + reverse("lettings:letting", args=[letting.id]) + ) + + assert response.context["title"] == letting.title + assert response.context["address"] == letting.address + + +@pytest.mark.django_db +def test_letting_detail_invalid_id(client): + """ + Test that accessing a non-existing letting + raises a DoesNotExist exception. + """ + with pytest.raises(Exception): + client.get(reverse("lettings:letting", args=[9999])) From 287fd384358a5f29ad5b7c702fc8fd044fadb71f Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 3 Mar 2026 18:14:58 +0100 Subject: [PATCH 030/113] Deleted unused imports --- lettings/tests.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lettings/tests.py b/lettings/tests.py index 4422f7011c..1a325fe107 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -54,9 +54,6 @@ def test_address_fields(address): one-to-one relationships work correctly. """ -import pytest -from lettings.models import Letting, Address - @pytest.mark.django_db def test_letting_str(letting): @@ -226,9 +223,7 @@ def test_letting_detail_view_status_code(client, letting): Test that the letting detail view returns HTTP 200 for a valid letting ID. """ - response = client.get( - reverse("lettings:letting", args=[letting.id]) - ) + response = client.get(reverse("lettings:letting", args=[letting.id])) assert response.status_code == 200 @@ -238,9 +233,7 @@ def test_letting_detail_view_template(client, letting): Test that the correct template is used for the letting detail view. """ - response = client.get( - reverse("lettings:letting", args=[letting.id]) - ) + response = client.get(reverse("lettings:letting", args=[letting.id])) assert "lettings/letting.html" in [t.name for t in response.templates] @@ -253,9 +246,7 @@ def test_letting_detail_context(client, letting): Args: letting (Letting): Fixture providing a sample Letting instance. """ - response = client.get( - reverse("lettings:letting", args=[letting.id]) - ) + response = client.get(reverse("lettings:letting", args=[letting.id])) assert response.context["title"] == letting.title assert response.context["address"] == letting.address From dc61b4124b4adc779984108d1e76a616cf1ec6ce Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 7 Mar 2026 09:53:42 +0100 Subject: [PATCH 031/113] Added unit tests for oc_letting_site.urls --- oc_lettings_site/tests.py | 84 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/oc_lettings_site/tests.py b/oc_lettings_site/tests.py index 3fd62bb718..87b10bd189 100644 --- a/oc_lettings_site/tests.py +++ b/oc_lettings_site/tests.py @@ -1,2 +1,82 @@ -def test_dummy(): - assert 1 +""" +URL tests for the main Django project (oc_lettings_site). + +This module verifies that the root URL configuration correctly +routes requests to the expected views or included URL modules. + +The following aspects are tested: +- URL reversing for the home page +- URL resolution for the home page +- HTTP response for the home page +- Accessibility of included URL patterns (lettings, profiles, admin) +""" + +import pytest +from django.urls import reverse, resolve +from oc_lettings_site import views + + +@pytest.mark.django_db +def test_index_url_reverse(): + """ + Test that the home page URL is correctly reversed. + + Ensures that the named route 'index' + generates the expected root path. + """ + url = reverse("index") + assert url == "/" + + +@pytest.mark.django_db +def test_index_url_resolves(): + """ + Test that the home page URL resolves + to the correct view function. + """ + resolver = resolve("/") + assert resolver.func == views.index + + +@pytest.mark.django_db +def test_index_http_response(client): + """ + Integration test ensuring that the home page + returns an HTTP 200 response. + """ + response = client.get(reverse("index")) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_lettings_url_accessible(client): + """ + Test that the lettings URL prefix is accessible + through the project-level URL configuration. + """ + response = client.get("/lettings/") + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_profiles_url_accessible(client): + """ + Test that the profiles URL prefix is accessible + through the project-level URL configuration. + """ + response = client.get("/profiles/") + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_admin_url_accessible(client): + """ + Test that the Django admin interface + is accessible through the project URL configuration. + + Note: + The admin page may redirect to the login page + if the user is not authenticated. + """ + response = client.get("/admin/") + assert response.status_code in (200, 302) From 285375cb06e5f604f86da427ef1589f362852269 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 7 Mar 2026 09:56:39 +0100 Subject: [PATCH 032/113] Added unit tests for oc_letting_site.views --- oc_lettings_site/tests.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/oc_lettings_site/tests.py b/oc_lettings_site/tests.py index 87b10bd189..cd37189c44 100644 --- a/oc_lettings_site/tests.py +++ b/oc_lettings_site/tests.py @@ -80,3 +80,32 @@ def test_admin_url_accessible(client): """ response = client.get("/admin/") assert response.status_code in (200, 302) + + +""" +View tests for the main Django project (oc_lettings_site). + +This module verifies that the project-level views return +correct HTTP responses and render the expected templates. + +The following view is tested: +- index +""" + + +@pytest.mark.django_db +def test_index_view_status_code(client): + """ + Test that the index view returns HTTP 200. + """ + response = client.get(reverse("index")) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_index_view_template(client): + """ + Test that the index view renders the correct template. + """ + response = client.get(reverse("index")) + assert "index.html" in [t.name for t in response.templates] From 7b81a3fe3cdfead090f0634f3731a75ef48ba622 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 7 Mar 2026 10:36:14 +0100 Subject: [PATCH 033/113] Added a conftest.py for profiles app --- lettings/tests.py | 2 +- profiles/conftest.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 profiles/conftest.py diff --git a/lettings/tests.py b/lettings/tests.py index 1a325fe107..9e09ec8cfe 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -45,7 +45,7 @@ def test_address_fields(address): assert address.country_iso_code == "USA" -# tests/test_models.py + """ Unit tests for the Letting model in lettings.models. diff --git a/profiles/conftest.py b/profiles/conftest.py new file mode 100644 index 0000000000..265a146457 --- /dev/null +++ b/profiles/conftest.py @@ -0,0 +1,44 @@ +""" +Models for the profiles application. + +This module defines the Profile model, which extends the built-in +Django User model with additional application-specific data, +such as the user's favorite city. +""" + +from django.db import models +from django.contrib.auth.models import User + + +class Profile(models.Model): + """ + Profile model extending Django's built-in User. + + Attributes: + user (OneToOneField): A one-to-one relationship with the User model, + ensuring each user has a single profile. The related_name + "new_user" allows reverse access from User instances. + favorite_city (CharField): Optional field storing the user's + favorite city, with a maximum length of 64 characters. + """ + + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="new_user") + favorite_city = models.CharField(max_length=64, blank=True) + + def __str__(self): + """ + Returns a string representation of the Profile instance. + + Returns: + str: The username of the associated User. + """ + return self.user.username + + class Meta: + """ + Metadata for the Profile model. + + Attributes: + db_table (str): Explicit database table name for the model. + """ + db_table = "profiles_profile" From 07c5a2687c87df6b3c7354eac171f4ab7331254c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 7 Mar 2026 10:45:38 +0100 Subject: [PATCH 034/113] Updated conftest, and created unit tests for profiles.models. --- profiles/conftest.py | 46 ++++++++++++++++++-------------------------- profiles/tests.py | 45 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/profiles/conftest.py b/profiles/conftest.py index 265a146457..6d2fc8b8a7 100644 --- a/profiles/conftest.py +++ b/profiles/conftest.py @@ -6,39 +6,31 @@ such as the user's favorite city. """ -from django.db import models +import pytest from django.contrib.auth.models import User +from profiles.models import Profile -class Profile(models.Model): - """ - Profile model extending Django's built-in User. - - Attributes: - user (OneToOneField): A one-to-one relationship with the User model, - ensuring each user has a single profile. The related_name - "new_user" allows reverse access from User instances. - favorite_city (CharField): Optional field storing the user's - favorite city, with a maximum length of 64 characters. +@pytest.fixture +def user(): """ + Fixture that creates a sample Django User instance. - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="new_user") - favorite_city = models.CharField(max_length=64, blank=True) + Returns: + User: A saved User instance with test credentials. + """ + return User.objects.create(username="testuser") - def __str__(self): - """ - Returns a string representation of the Profile instance. - Returns: - str: The username of the associated User. - """ - return self.user.username +@pytest.fixture +def profile(user): + """ + Fixture that creates a sample Profile instance linked to a User. - class Meta: - """ - Metadata for the Profile model. + Args: + user (User): Fixture providing a User instance. - Attributes: - db_table (str): Explicit database table name for the model. - """ - db_table = "profiles_profile" + Returns: + Profile: A saved Profile instance with sample data. + """ + return Profile.objects.create(user=user, favorite_city="Paris") diff --git a/profiles/tests.py b/profiles/tests.py index 7ce503c2dd..53cc39ff00 100644 --- a/profiles/tests.py +++ b/profiles/tests.py @@ -1,3 +1,44 @@ -from django.test import TestCase +""" +Unit tests for the Profile model in profiles.models. -# Create your tests here. +This module verifies: +- The string representation of Profile +- Correct field values +- Relationship with Django User +""" + +import pytest + + +@pytest.mark.django_db +def test_profile_str(profile): + """ + Test the string representation of the Profile model. + + The __str__ method should return the username + of the associated user. + """ + assert str(profile) == "testuser" + + +@pytest.mark.django_db +def test_profile_fields(profile, user): + """ + Test that Profile fields are correctly stored. + + Args: + profile (Profile): Fixture providing a Profile instance. + user (User): Fixture providing a User instance. + """ + assert profile.user == user + assert profile.favorite_city == "Paris" + + +@pytest.mark.django_db +def test_profile_user_relationship(profile): + """ + Test the one-to-one relationship between User and Profile. + + Ensures the related_name 'new_user' works correctly. + """ + assert profile.user.new_user == profile From f93c4e627334df0719d1f9e94741742faef4c2d1 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 7 Mar 2026 10:47:40 +0100 Subject: [PATCH 035/113] Updated conftest, and created unit tests for profiles.urls --- lettings/tests.py | 1 - profiles/tests.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/lettings/tests.py b/lettings/tests.py index 9e09ec8cfe..bd82e08129 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -45,7 +45,6 @@ def test_address_fields(address): assert address.country_iso_code == "USA" - """ Unit tests for the Letting model in lettings.models. diff --git a/profiles/tests.py b/profiles/tests.py index 53cc39ff00..ae33d79d8a 100644 --- a/profiles/tests.py +++ b/profiles/tests.py @@ -8,6 +8,8 @@ """ import pytest +from django.urls import reverse, resolve +from profiles import views @pytest.mark.django_db @@ -42,3 +44,56 @@ def test_profile_user_relationship(profile): Ensures the related_name 'new_user' works correctly. """ assert profile.user.new_user == profile + + +""" +URL tests for the profiles application. + +This module verifies that URL patterns defined in profiles.urls +correctly resolve to the expected view functions and that +URL reversing works as intended. +""" + + +@pytest.mark.django_db +def test_profiles_index_url_reverse(): + """ + Test that the profiles index URL is correctly reversed. + + Ensures the 'profiles:profiles_index' named route + generates the expected URL path. + """ + url = reverse("profiles:profiles_index") + assert url == "/profiles/" + + +@pytest.mark.django_db +def test_profiles_index_url_resolves(): + """ + Test that the profiles index URL resolves + to the correct view function. + """ + resolver = resolve("/profiles/") + assert resolver.func == views.profiles_index + + +@pytest.mark.django_db +def test_profile_detail_url_reverse(): + """ + Test that the profile detail URL is correctly reversed. + + Ensures the 'profiles:profile' named route generates + the expected URL when provided with a username. + """ + url = reverse("profiles:profile", args=["testuser"]) + assert url == "/profiles/testuser/" + + +@pytest.mark.django_db +def test_profile_detail_url_resolves(): + """ + Test that the profile detail URL resolves + to the correct view function. + """ + resolver = resolve("/profiles/testuser/") + assert resolver.func == views.profile From 8fabd7e47758ad44249b3ee2e6b3ac570e4c2ea4 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 7 Mar 2026 10:50:37 +0100 Subject: [PATCH 036/113] Updated conftest, and created unit tests for profiles.views --- profiles/tests.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/profiles/tests.py b/profiles/tests.py index ae33d79d8a..f1f7fdf8cb 100644 --- a/profiles/tests.py +++ b/profiles/tests.py @@ -97,3 +97,86 @@ def test_profile_detail_url_resolves(): """ resolver = resolve("/profiles/testuser/") assert resolver.func == views.profile + + +""" +View tests for the profiles application. + +This module verifies that profile-related views return the correct +HTTP responses, use the expected templates, and provide the correct +context data. +""" + + +@pytest.mark.django_db +def test_profiles_index_view_status_code(client, profile): + """ + Test that the profiles index view returns HTTP 200. + + Args: + client (Client): Django test client. + profile (Profile): Fixture providing a Profile instance. + """ + response = client.get(reverse("profiles:profiles_index")) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_profiles_index_view_template(client, profile): + """ + Test that the profiles index view uses the correct template. + """ + response = client.get(reverse("profiles:profiles_index")) + assert "profiles/index.html" in [t.name for t in response.templates] + + +@pytest.mark.django_db +def test_profiles_index_context(client, profile): + """ + Test that the profiles index view includes profiles in context. + """ + response = client.get(reverse("profiles:profiles_index")) + assert "profiles_list" in response.context + assert profile in response.context["profiles_list"] + + +@pytest.mark.django_db +def test_profile_detail_view_status_code(client, profile): + """ + Test that the profile detail view returns HTTP 200 + for an existing profile. + """ + response = client.get(reverse("profiles:profile", args=[profile.user.username])) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_profile_detail_view_template(client, profile): + """ + Test that the profile detail view uses the correct template. + """ + response = client.get(reverse("profiles:profile", args=[profile.user.username])) + assert "profiles/profile.html" in [t.name for t in response.templates] + + +@pytest.mark.django_db +def test_profile_detail_context(client, profile): + """ + Test that the profile detail view passes the correct + profile object to the template context. + """ + response = client.get(reverse("profiles:profile", args=[profile.user.username])) + assert "profile" in response.context + assert response.context["profile"] == profile + + +@pytest.mark.django_db +def test_profile_detail_nonexistent(client): + """ + Test that requesting a non-existing profile raises an error. + + Since the view uses Profile.objects.get(), Django will raise + a Profile.DoesNotExist exception, resulting in a server error. + """ + with pytest.raises(Exception): + client.get(reverse("profiles:profile", args=["unknownuser"])) From 7cf6d2f9c32290ec752c318944c624defeb59026 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 9 Mar 2026 16:36:13 +0100 Subject: [PATCH 037/113] "Added integration tests for lettings app with happy, edge and TDD 404 scenarios" --- lettings/tests.py | 189 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/lettings/tests.py b/lettings/tests.py index bd82e08129..4f60505de7 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -259,3 +259,192 @@ def test_letting_detail_invalid_id(client): """ with pytest.raises(Exception): client.get(reverse("lettings:letting", args=[9999])) + + +""" +Integration tests for the `lettings` Django application. + +These tests validate the behavior of the application by exercising +the full request/response cycle of the Django stack: + + URL routing → View execution → Database access → Template rendering → HTTP response + +Unlike unit tests that focus on isolated components (models, views, or URLs), +integration tests ensure that multiple layers of the application work together +correctly when accessed through real HTTP requests. + +The following scenarios are covered: + +Happy paths: + - Accessing the lettings index page successfully + - Displaying existing lettings in the index page + - Navigating to the detail page of a specific letting + - Displaying letting address information correctly + +Edge cases: + - Rendering the index page when no lettings exist + +Sad paths (TDD approach): + - Requesting a letting that does not exist should return HTTP 404 + +Some tests intentionally document expected future behavior such as +proper 404 error handling. These tests may initially fail until the +application implements the appropriate error management. + +This approach follows Test-Driven Development (TDD) principles. +""" + + +@pytest.mark.django_db +def test_lettings_index_page_accessible(client): + """ + Verify that the lettings index page is accessible via HTTP. + + This test ensures that the URL associated with the lettings index + view is correctly configured and returns a valid HTTP response. + + The test validates that: + - The URL can be resolved using Django's reverse function + - The HTTP response status code is 200 (OK) + - The correct template is used to render the page + + This confirms that URL routing, view execution, and template + rendering work correctly together. + """ + url = reverse("lettings:lettings_index") + + response = client.get(url) + + assert response.status_code == 200 + assert "lettings/index.html" in [t.name for t in response.templates] + + +@pytest.mark.django_db +def test_lettings_index_displays_existing_lettings(client, letting): + """ + Verify that existing lettings are displayed on the index page. + + This test creates a Letting instance using a fixture and ensures + that the index page correctly retrieves and renders the letting + information. + + The test validates that: + - The letting instance is retrieved from the database + - The letting title appears in the rendered HTML response + + This confirms that the view properly interacts with the database + and passes the expected context data to the template. + """ + url = reverse("lettings:lettings_index") + + response = client.get(url) + + content = response.content.decode() + + assert letting.title in content + + +@pytest.mark.django_db +def test_lettings_index_handles_empty_dataset(client): + """ + Verify the behavior of the index page when no lettings exist. + + When the database contains no Letting instances, the template + should display a fallback message informing the user that + no lettings are available. + + The test validates that: + - The page still renders successfully + - The appropriate message appears in the response + + This ensures the application handles empty datasets gracefully. + """ + url = reverse("lettings:lettings_index") + + response = client.get(url) + + content = response.content.decode() + + assert response.status_code == 200 + assert "No lettings are available." in content + + +@pytest.mark.django_db +def test_letting_detail_page_accessible(client, letting): + """ + Verify that a letting detail page can be accessed successfully. + + This test ensures that a valid letting ID correctly resolves + to the letting detail view and that the page renders without errors. + + The test validates that: + - The correct URL is generated using reverse() + - The view retrieves the letting from the database + - The correct template is used to render the page + - The HTTP response status is 200 (OK) + + This confirms the correct integration of URL routing, + database access, and template rendering. + """ + url = reverse("lettings:letting", args=[letting.id]) + + response = client.get(url) + + assert response.status_code == 200 + assert "lettings/letting.html" in [t.name for t in response.templates] + + +@pytest.mark.django_db +def test_letting_detail_displays_address_information(client, letting): + """ + Verify that the letting detail page displays address information. + + This test ensures that the address fields associated with the + letting instance are correctly passed to the template and rendered + in the final HTML output. + + The test checks that the response contains: + - the letting title + - the street name + - the city name + + This confirms that the relationship between Letting and Address + models is correctly handled and displayed by the view and template. + """ + url = reverse("lettings:letting", args=[letting.id]) + + response = client.get(url) + + content = response.content.decode() + + assert letting.title in content + assert letting.address.street in content + assert letting.address.city in content + + +@pytest.mark.django_db +def test_letting_detail_returns_404_for_unknown_letting(client): + """ + Verify that requesting a non-existent letting returns HTTP 404. + + This test represents the expected behavior for the application + when a user attempts to access a letting that does not exist + in the database. + + According to REST and Django best practices, the application + should return an HTTP 404 (Not Found) response instead of + raising an unhandled exception. + + This test follows a Test-Driven Development (TDD) approach: + - It defines the expected behavior first + - The test may initially fail if the view does not yet + implement proper 404 handling + + The test will pass once the view uses Django's `get_object_or_404` + helper or equivalent error handling. + """ + url = reverse("lettings:letting", args=[9999]) + + response = client.get(url) + + assert response.status_code == 404 From 8e050d5e05ccaeaf96ceff52affda7868da33ae3 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 9 Mar 2026 16:45:52 +0100 Subject: [PATCH 038/113] Added integration tests for oc_letting_site app with happy, sad and edge cases --- lettings/tests.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/lettings/tests.py b/lettings/tests.py index 4f60505de7..eb2c689997 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -448,3 +448,176 @@ def test_letting_detail_returns_404_for_unknown_letting(client): response = client.get(url) assert response.status_code == 404 + + +""" +Integration tests for the main Django project application `oc_lettings_site`. + +These tests validate the behavior of the project's root-level views and +their integration with the rest of the Django application stack. + +Unlike unit tests that verify isolated components, these tests exercise +the full request-response lifecycle: + + URL routing → View execution → Template rendering → HTTP response + +The main goal of these tests is to ensure that the project’s entry point +(the home page) behaves correctly and provides valid navigation to the +different Django applications included in the project. + +The following scenarios are covered: + +Happy paths: + - Accessing the home page successfully + - Rendering the correct template for the home page + - Displaying navigation links to the lettings and profiles sections + +Integration validation: + - Ensuring the navigation links resolve to valid URLs + - Verifying that cross-application routing is correctly configured + +Sad paths (TDD preparation): + - Documenting expected 404 behavior for unknown routes + +Some tests may initially fail if global error handling (404 / 500 pages) +is not yet implemented. This follows a Test-Driven Development (TDD) +approach where expected behaviors are specified before implementation. +""" + + +@pytest.mark.django_db +def test_homepage_accessible(client): + """ + Verify that the project home page is accessible. + + This test ensures that the root URL of the application resolves + correctly to the index view and returns a valid HTTP response. + + The test validates that: + - Django can resolve the URL using reverse() + - The HTTP response status code is 200 (OK) + + This confirms that the project's main entry point is correctly + configured and accessible to users. + """ + url = reverse("index") + + response = client.get(url) + + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_homepage_uses_correct_template(client): + """ + Verify that the home page uses the correct template. + + The index view should render the 'index.html' template. This test + ensures that the template rendering layer is correctly connected + to the view. + + This confirms the integration between: + - URL routing + - view execution + - template rendering + """ + url = reverse("index") + + response = client.get(url) + + templates = [t.name for t in response.templates] + + assert "index.html" in templates + + +@pytest.mark.django_db +def test_homepage_displays_navigation_links(client): + """ + Verify that the home page displays navigation links to the main + sections of the application. + + The index page should contain links allowing users to navigate + to both the lettings and profiles sections. + + This test validates that: + - the link to the lettings index page is present + - the link to the profiles index page is present + + This confirms that the template correctly integrates URL + resolution using Django's `{% url %}` template tag. + """ + url = reverse("index") + + response = client.get(url) + + content = response.content.decode() + + lettings_url = reverse("lettings:lettings_index") + profiles_url = reverse("profiles:profiles_index") + + assert lettings_url in content + assert profiles_url in content + + +@pytest.mark.django_db +def test_navigation_to_lettings_page(client): + """ + Verify that the lettings section is reachable from the project. + + This test ensures that the root URL configuration correctly + includes the URL patterns defined in the `lettings` application. + + The test validates that: + - the lettings index URL resolves correctly + - accessing the page returns HTTP 200 + + This confirms that cross-application URL inclusion works as expected. + """ + url = reverse("lettings:lettings_index") + + response = client.get(url) + + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_navigation_to_profiles_page(client): + """ + Verify that the profiles section is reachable from the project. + + This test ensures that the root URL configuration correctly + includes the URL patterns defined in the `profiles` application. + + The test validates that: + - the profiles index URL resolves correctly + - accessing the page returns HTTP 200 + + This confirms that cross-application routing is properly configured. + """ + url = reverse("profiles:profiles_index") + + response = client.get(url) + + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_unknown_url_returns_404(client): + """ + Verify that requesting an unknown URL returns HTTP 404. + + According to standard web application behavior, when a user + requests a route that does not exist, the server should return + a 404 (Not Found) response. + + This test documents the expected behavior for invalid routes. + + It also prepares the test suite for future implementation of + custom 404 error pages within the project. + + This test follows a Test-Driven Development (TDD) approach and + may evolve once dedicated error handlers are implemented. + """ + response = client.get("/this-page-does-not-exist/") + + assert response.status_code == 404 From 4b88e4b5a618a6651462229fe13878fa0a15d12e Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 9 Mar 2026 16:48:19 +0100 Subject: [PATCH 039/113] Added integration tests for oc_letting_site app with happy, sad, edge cases and TDD oriented fore next error 404 management. --- profiles/tests.py | 217 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) diff --git a/profiles/tests.py b/profiles/tests.py index f1f7fdf8cb..6cfbf79ffb 100644 --- a/profiles/tests.py +++ b/profiles/tests.py @@ -9,6 +9,7 @@ import pytest from django.urls import reverse, resolve +from django.contrib.auth.models import User from profiles import views @@ -180,3 +181,219 @@ def test_profile_detail_nonexistent(client): """ with pytest.raises(Exception): client.get(reverse("profiles:profile", args=["unknownuser"])) + + +""" +Integration tests for the `profiles` Django application. + +These tests validate the correct behavior of the profiles application +by exercising the full Django request-response lifecycle: + + URL routing → View execution → Database queries → Template rendering → HTTP response + +Unlike unit tests that focus on isolated components such as models +or individual view functions, these integration tests ensure that +multiple layers of the application interact correctly when accessed +through HTTP requests. + +The tests cover several categories of behavior: + +Happy paths: + - Accessing the profiles index page successfully + - Rendering existing profiles in the index page + - Navigating to a specific profile page + - Displaying profile details retrieved from the database + +Edge cases: + - Handling situations where no profiles exist + +Sad paths (Test-Driven Development approach): + - Requesting a profile that does not exist should return HTTP 404 + - Requesting a user without an associated profile should return HTTP 404 + +Some tests intentionally document expected behaviors that may not yet +be implemented in the application. These tests may initially fail +until proper error handling is introduced (for example using +Django's `get_object_or_404` helper). + +This follows a Test-Driven Development (TDD) approach where expected +application behavior is specified before implementing the logic. +""" + + +@pytest.mark.django_db +def test_profiles_index_page_accessible(client): + """ + Verify that the profiles index page is accessible. + + This test ensures that the URL associated with the profiles + index view resolves correctly and returns a valid HTTP response. + + The test validates that: + - the URL can be generated using Django's reverse function + - the view returns HTTP status code 200 + - the request completes without errors + + This confirms that URL routing, view execution, and template + rendering are correctly integrated. + """ + url = reverse("profiles:profiles_index") + + response = client.get(url) + + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_profiles_index_uses_correct_template(client): + """ + Verify that the profiles index view renders the correct template. + + The view should render the 'profiles/index.html' template. + This test ensures that the template layer is correctly + connected to the view logic. + """ + url = reverse("profiles:profiles_index") + + response = client.get(url) + + templates = [t.name for t in response.templates] + + assert "profiles/index.html" in templates + + +@pytest.mark.django_db +def test_profiles_index_displays_existing_profiles(client, profile): + """ + Verify that existing profiles are displayed on the index page. + + This test creates a Profile instance using a fixture and ensures + that the username associated with the profile appears in the + rendered HTML response. + + This confirms that: + - profiles are correctly retrieved from the database + - the context variable `profiles_list` is passed to the template + - the template correctly renders profile usernames. + """ + url = reverse("profiles:profiles_index") + + response = client.get(url) + + content = response.content.decode() + + assert profile.user.username in content + + +@pytest.mark.django_db +def test_profiles_index_handles_empty_dataset(client): + """ + Verify the behavior of the index page when no profiles exist. + + If the database contains no Profile instances, the template + should display a fallback message informing the user that + no profiles are available. + + This ensures the application handles empty datasets gracefully. + """ + url = reverse("profiles:profiles_index") + + response = client.get(url) + + content = response.content.decode() + + assert response.status_code == 200 + assert "No profiles are available." in content + + +@pytest.mark.django_db +def test_profile_detail_page_accessible(client, profile): + """ + Verify that the profile detail page is accessible for an existing user. + + This test ensures that the dynamic URL using the username parameter + resolves correctly and retrieves the associated profile. + + The test validates that: + - the URL is correctly generated with reverse() + - the view returns HTTP status code 200 + - the profile template is used for rendering the response. + """ + url = reverse("profiles:profile", args=[profile.user.username]) + + response = client.get(url) + + assert response.status_code == 200 + assert "profiles/profile.html" in [t.name for t in response.templates] + + +@pytest.mark.django_db +def test_profile_detail_displays_user_information(client, profile): + """ + Verify that the profile detail page displays user information. + + The template should render fields associated with the User model + as well as the Profile model itself. + + This test ensures that the following data appears in the response: + - username + - favorite city + + This confirms the correct integration between the Profile model, + the related User model, the view logic, and the template rendering. + """ + url = reverse("profiles:profile", args=[profile.user.username]) + + response = client.get(url) + + content = response.content.decode() + + assert profile.user.username in content + assert profile.favorite_city in content + + +@pytest.mark.django_db +def test_profile_detail_returns_404_for_unknown_username(client): + """ + Verify that requesting a profile with an unknown username + returns HTTP 404. + + According to standard web application behavior, requesting + a resource that does not exist should return a 404 response. + + This test documents the expected behavior of the application + when the requested profile does not exist in the database. + + The current implementation may raise an unhandled exception + which results in a 500 error. This test follows a + Test-Driven Development approach and will pass once proper + error handling is implemented. + """ + url = reverse("profiles:profile", args=["unknown_user"]) + + response = client.get(url) + + assert response.status_code == 404 + + +@pytest.mark.django_db +def test_profile_detail_user_without_profile_returns_404(client): + """ + Verify that requesting a user without an associated profile + returns HTTP 404. + + In some cases a Django User may exist without a corresponding + Profile instance. The application should handle this situation + gracefully and return a 404 response rather than raising an + unhandled exception. + + This test documents the expected behavior and may initially + fail until proper error handling is implemented in the view. + """ + user = User.objects.create(username="orphan_user") + + url = reverse("profiles:profile", args=[user.username]) + + response = client.get(url) + + assert response.status_code == 404 From 3f2c22663675c99529def8769529e7fd9b9a3654 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 9 Mar 2026 17:10:00 +0100 Subject: [PATCH 040/113] Downloaded pytest-coverage, and created a conf test for it, coverage objective achieved. --- .coverage | Bin 0 -> 53248 bytes .coveragerc | 5 +++++ requirements.txt | Bin 910 -> 984 bytes 3 files changed, 5 insertions(+) create mode 100644 .coverage create mode 100644 .coveragerc diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..9ab758f19a1dee6b4c92d94bc07042d8d352d7c9 GIT binary patch literal 53248 zcmeI4TWlOx8OP6Dc6MiXcF%4c?`9JckHf`rYdbNnlu(djH%UVwC~oVtAenB)yJzhQ zyE~hi*(3&`dfh_Ph6hv$DIgv!@z78t1P=&_7OA3AK~<@Wgv8|m^1vkz2#P2#fDqrA z+pgodsuax2*$zFGK& zOA^)I@{QiHj+QDwIOn?3f>iMfRn`oi3Or%vepG5tVVhh=h7pVMvY zqTXh2| z47&=S%%}hUwnoEHET&LOQ#(^CEg^ z_i|2n)o580Is4%FMYSkRy^HLRu0zUFz1!yrJQEeMmNm`jTZen9=h42FUS%1J^Vi-P5qOS324&!&&8JEMFM&G=-fDZ{`;u z=)(Adjb_tm)q?(tcf)?E(Dw7-@f_=-;pkPH!O5U6^@IVjm77)h#e5vjgl(u_ zoAEK}?R|ns2_OL^fCP{L5+<&!wg<$^8SYw+qT6g6FG}evIBFJx_gH~ z*U^FNvZ@uBWbMhUz{Fv;c+pulJ$*alsm}=X4G+b_t|7e0uDKncYjogtg(-G?GAlUR za3L0;q5$aF4KDzO^Rdts88Fnfmx8Vs24<+Qm;-&&19z~sVTa>G4CXf1xj3$EUk0?z4P4unZXmMizH~eVM%FE=v2eSn9f`;lQ}GfO5i2J_ z*x=(KBGw-dMI{!{qSno*HO&?e)M6qQ!Xjvqu#ybIW(MAZM8x1unE5??Ns5KC2w6|r zUJ;bVu#XQ12crV$J2`NhZs`@AV5k5A!QcNE#cM=+Q(nuwkb8e-zcQD8I)I0H!T5hTuKI%U|IV1$HR@afyAE}!ygoX3yGG;x9WenF1wb&4EyP4uWWYdIKCX@* z=%d||ygog62aD_(j(2i#6BdqlvfbVC`r#N6>l^QA-4S_xZt&W+bOWJPGjSCdS~ndN zw~N}5kenJfVIi?pOyVIT))x=Sm_UnKw{V!P#6(yGEfkjM?vmGM2H%22#G+BQ91~>` zvPhW}*FHYDi4?n|^7=`2;DeSey+YskAOHU!j;@ga58ufHU7 zNfP;*a7J0XOS)DkztqU{Wir2u+?B~e#$B>lC&iU~Itx2RO?-V;$)_}4wj%y%R?WcH z&WxxMK~3{bDz2S*c1VTI18MQ4@>S!@<@#0OL!|o3a7tFyDUoc*_huMWM zj99Q``ILSLl6qtEiI;erH^01`j~NB{{S0VIF~kN^@u0!RP}+$I73m_W!C zyj3CZt+Lcx$vJPOWxbW+Jx~xb8E=oKy;WLGz+(s@1{UDxdIHDcM^|lDA5V z{5gc0;P3xQ{x+3kb|ioVkN^@u0!RP}AOR$R1dsp{Kmtf0)&%(be;ohE+C(T02_OL^ zfCP{L5 Date: Sat, 4 Apr 2026 14:23:10 +0200 Subject: [PATCH 041/113] Corrected pluralisation error on admin panel by adding verbose_name_plural in concerned model. --- lettings/models.py | 2 ++ oc-lettings-site.sqlite3 | Bin 151552 -> 151552 bytes 2 files changed, 2 insertions(+) diff --git a/lettings/models.py b/lettings/models.py index 8371f84bd9..4c465bd4b4 100644 --- a/lettings/models.py +++ b/lettings/models.py @@ -56,6 +56,8 @@ class Meta: """ db_table = "lettings_address" + verbose_name = "Address" + verbose_name_plural = "Addresses" class Letting(models.Model): diff --git a/oc-lettings-site.sqlite3 b/oc-lettings-site.sqlite3 index 533a920216ed2f75ae2d3a31c60b114e589a0f15..bff34c680fdad900aa001828cb9ea66bc30c3db9 100644 GIT binary patch delta 287 zcmZozz}c{XbAmLZ`$QRMM)!>g(euU4bPY^^NWsv^%E-vdz);W7($v)4p!w4L?U&{= z8Z~e*@wPDV9pY`-*jT}<(PYiU>f6HVSY%OXnw^uMR+w0pYh00@U6h}lmSmJ|kXBM! zm~L8_IsKI$(|>ue*@hMfvyBW4EKQ7>tQoRQi_p-iGMo-{~iAAn*|G2 z^Q)>b`!W);f@%64dnOH_#={K!kN6L77HrtgZ!5>_$Y^9>gu`McS!PF0ptOOmftjw6 Vg@S>lm5HI1shOUog^7iQIRN(@QSJZ$ delta 143 zcmZozz}c{XbAmLZ%S0JxMwg8V(euR(bPddO4NVjbEUiootxV1IEX_>~%}tsw&EI}$ zKBG|s8zbK#2EIcZ8+Y(=G+8sU`nIq-PJdv}q_F*!9+MO!8zcW62L3yn1sg8$PrqZ& jqyZFo#K8XuC~$*cPL|n`)5yRGY=@Brk{uQ%78d3J7eXlR From f19a9b0a919151737848eb4566e7094b8833001d Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 4 Apr 2026 17:21:36 +0200 Subject: [PATCH 042/113] Added proper error 404 and 500 handling, with dedicated templates and tests. --- .coverage | Bin 53248 -> 53248 bytes global_templates/404.html | 20 ++++++++++++++++++++ global_templates/500.html | 23 +++++++++++++++++++++++ lettings/tests.py | 13 ++++++++----- lettings/views.py | 5 +++-- oc_lettings_site/urls.py | 17 ++++++++++------- oc_lettings_site/views.py | 8 ++++++++ profiles/tests.py | 13 +++++++------ profiles/views.py | 25 ++++++++++++++----------- 9 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 global_templates/404.html create mode 100644 global_templates/500.html diff --git a/.coverage b/.coverage index 9ab758f19a1dee6b4c92d94bc07042d8d352d7c9..8aacaee84491693943f48c80a4e4d2625a0002ce 100644 GIT binary patch delta 271 zcmZozz}&Eac>`O6$^r)dFZ@sVFY;IM7w{+XNANrH>+mb^i}G{x{pb6{w}WpT--6A8 z0%d%1Jc2BYoKk#DJ`4p+3>F*$K%S5w(}pe!ejux8^1(iFSqDxAAYfzAVqV3=5Xu&C zmSKe?Lx?fM0wabH#>rp$ba`iTaRF6}@iR4qit?RT!`O6%3=ooFZ@sVFY;IO7xE|bNAf%I>+&n{i}UmHGxB}m+s(I;Z}Db9 zfl9u~3;K8k**OGQ7&(OmnKpEp^8;B0llS$B%N$@~00L%)sf?j)4O+}A(ipDrFszbf z2#{dVn*6Cxmxqy)3#eFxpQ#~q(qx@}XQ4O_R**RYOaa{?EUb*2jT)01`xPAx7=h;S zfOLmQGK3g2EJ$N;C}m@CPz0ObaEc)ym0`gx9-u`Fj2IROG6T&}VP;}5W_Tc;k;0&J Lfn)QNei;V +

404 - Page not found

+ +

+ Sorry, the page you are looking for does not exist. + Maybe you were trying to access something that does not exist in our database? +

+ + + Return to home + +
+ +{% endblock %} \ No newline at end of file diff --git a/global_templates/500.html b/global_templates/500.html new file mode 100644 index 0000000000..2790a0c469 --- /dev/null +++ b/global_templates/500.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %}Server error{% endblock %} + +{% block content %} + +
+

500 - Internal server error

+ +

+ Something went wrong on our side. +

+ +

+ Our team has been notified and is working on it. +

+ + + Return to home + +
+ +{% endblock %} \ No newline at end of file diff --git a/lettings/tests.py b/lettings/tests.py index eb2c689997..617d68d3b8 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -252,13 +252,16 @@ def test_letting_detail_context(client, letting): @pytest.mark.django_db -def test_letting_detail_invalid_id(client): +def test_letting_detail_invalid_id_returns_404(client): """ - Test that accessing a non-existing letting - raises a DoesNotExist exception. + Ensure that requesting a non-existing letting returns HTTP 404. + + The view now uses get_object_or_404, which should return a + 404 response instead of raising an exception. """ - with pytest.raises(Exception): - client.get(reverse("lettings:letting", args=[9999])) + response = client.get(reverse("lettings:letting", args=[9999])) + + assert response.status_code == 404 """ diff --git a/lettings/views.py b/lettings/views.py index bb42ce8715..b5400811d3 100644 --- a/lettings/views.py +++ b/lettings/views.py @@ -8,7 +8,7 @@ and renders the appropriate HTML template with a context dictionary. """ -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from django.http import HttpRequest, HttpResponse from .models import Letting @@ -45,8 +45,9 @@ def letting(request: HttpRequest, letting_id: int) -> HttpResponse: :return: Rendered HTML page displaying the letting details. :rtype: HttpResponse :raises Letting.DoesNotExist: If no letting matches the given ID. + :raises Http404: If no letting matches the given ID. """ - letting = Letting.objects.get(id=letting_id) + letting = get_object_or_404(Letting, id=letting_id) context = { "title": letting.title, "address": letting.address, diff --git a/oc_lettings_site/urls.py b/oc_lettings_site/urls.py index b3c41bba76..958af66e31 100644 --- a/oc_lettings_site/urls.py +++ b/oc_lettings_site/urls.py @@ -19,14 +19,17 @@ urlpatterns = [ # Home page - path('', views.index, name='index'), - + path("", views.index, name="index"), # Lettings app: routes are defined in lettings.urls - path('lettings/', include('lettings.urls')), - + path("lettings/", include("lettings.urls")), # Profiles app: routes are defined in profiles.urls - path('profiles/', include('profiles.urls')), - + path("profiles/", include("profiles.urls")), # Django admin interface - path('admin/', admin.site.urls), + path("admin/", admin.site.urls), ] + + +# Error handling: + +handler404 = "oc_lettings_site.views.page_not_found" +handler500 = "oc_lettings_site.views.server_error" diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index cd324ed64c..79bdb20952 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -22,3 +22,11 @@ def index(request): HttpResponse: Rendered home page template. """ return render(request, "index.html") + + +def page_not_found(request, exception): + return render(request, "404.html", status=404) + + +def server_error(request): + return render(request, "500.html", status=500) diff --git a/profiles/tests.py b/profiles/tests.py index 6cfbf79ffb..b6e3690363 100644 --- a/profiles/tests.py +++ b/profiles/tests.py @@ -172,15 +172,16 @@ def test_profile_detail_context(client, profile): @pytest.mark.django_db -def test_profile_detail_nonexistent(client): +def test_profile_detail_nonexistent_returns_404(client): """ - Test that requesting a non-existing profile raises an error. + Ensure that requesting a non-existing profile returns HTTP 404. - Since the view uses Profile.objects.get(), Django will raise - a Profile.DoesNotExist exception, resulting in a server error. + The view uses get_object_or_404, so a missing profile should + result in a 404 response instead of an exception. """ - with pytest.raises(Exception): - client.get(reverse("profiles:profile", args=["unknownuser"])) + response = client.get(reverse("profiles:profile", args=["unknownuser"])) + + assert response.status_code == 404 """ diff --git a/profiles/views.py b/profiles/views.py index 1fced94d7a..c198faa399 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -7,7 +7,7 @@ the corresponding templates for rendering. """ -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from .models import Profile @@ -19,11 +19,11 @@ def profiles_index(request): to the 'profiles/index.html' template under the context variable 'profiles_list'. - Args: - request (HttpRequest): The HTTP request object. + :param request: The HTTP request object. + :type request: HttpRequest - Returns: - HttpResponse: Rendered HTML page with the list of profiles. + :return: Rendered HTML page with the list of profiles. + :rtype: HttpResponse """ profiles_list = Profile.objects.all() context = {"profiles_list": profiles_list} @@ -38,13 +38,16 @@ def profile(request, username): to the provided username and passes it to the 'profiles/profile.html' template under the context variable 'profile'. - Args: - request (HttpRequest): The HTTP request object. - username (str): The username of the user whose profile is requested. + :param request: The HTTP request object. + :type request: HttpRequest + :param username: The username of the user whose profile is requested. + :type username: str - Returns: - HttpResponse: Rendered HTML page with the profile details. + :return: Rendered HTML page with the profile details. + :rtype: HttpResponse + + :raises Http404: If no profile matches the given username. """ - profile = Profile.objects.get(user__username=username) + profile = get_object_or_404(Profile, user__username=username) context = {"profile": profile} return render(request, "profiles/profile.html", context) From 6ab28e296bfcc577fb2940469734570442fa9d28 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 4 Apr 2026 18:52:51 +0200 Subject: [PATCH 043/113] Updated tests to include an error 500 test, and updated a few docstrings. --- .coverage | Bin 53248 -> 53248 bytes lettings/tests.py | 3 ++- oc_lettings_site/tests.py | 16 ++++++++++++++++ oc_lettings_site/views.py | 35 ++++++++++++++++++++++++++++------- profiles/tests.py | 2 ++ 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/.coverage b/.coverage index 8aacaee84491693943f48c80a4e4d2625a0002ce..f087af94c5222e2f068528c39125618920f8e139 100644 GIT binary patch delta 227 zcmZozz}&Eac>`O6%4`PyFZ@sVFY;IM7w{+XNANrHYw^qR3-Gh?{o;Gex0!Dx-|WqT z0)>2&7xbCQ2r~cy4?_rBh$O=*o&~cQ1B@9~q%k<8GOS>m{HagbkcXRvkyDDBiL-&F zfklCf3&<1WXKDx)QoFpmMFgz>X`lO6jV{h12bCN$1xU`}!H zWiT*cPMg7Ke5QeuA-PTR0#I#3+5#4!wFV48%K}mv0?slt%wlA?D#^fL$_Nyf06M2Z Xi<#kS8p8@6hN+AUyqhof>pK7ds~S3% delta 222 zcmZozz}&Eac>`O6$^r)dFZ@sVFY;IM7w{+XNANrH>+mb^i}G{x{pb6{w}WpT--6A8 z0%d%YXZM-Oa54Y^8-o_}DjtSVwt%w?DiW51(6%L5(-W_w1q6AiN%5)#+JL>?lGJ#=18pDE93=3v4GMF+mtTJX`&|(CNOE846HE1z2TuozG Q!NV|>k%4vd#eRJU0J;}9R{#J2 diff --git a/lettings/tests.py b/lettings/tests.py index 617d68d3b8..0a48fe477e 100644 --- a/lettings/tests.py +++ b/lettings/tests.py @@ -1,4 +1,3 @@ -# tests/test_models.py """ Unit tests for the lettings.models module. @@ -451,6 +450,7 @@ def test_letting_detail_returns_404_for_unknown_letting(client): response = client.get(url) assert response.status_code == 404 + assert "Sorry, the page you are looking for does not exist." in response.content.decode() """ @@ -624,3 +624,4 @@ def test_unknown_url_returns_404(client): response = client.get("/this-page-does-not-exist/") assert response.status_code == 404 + assert "Sorry, the page you are looking for does not exist." in response.content.decode() diff --git a/oc_lettings_site/tests.py b/oc_lettings_site/tests.py index cd37189c44..1f57c6c8f3 100644 --- a/oc_lettings_site/tests.py +++ b/oc_lettings_site/tests.py @@ -15,6 +15,9 @@ from django.urls import reverse, resolve from oc_lettings_site import views +from django.test import RequestFactory +from django.core.handlers.exception import response_for_exception + @pytest.mark.django_db def test_index_url_reverse(): @@ -109,3 +112,16 @@ def test_index_view_template(client): """ response = client.get(reverse("index")) assert "index.html" in [t.name for t in response.templates] + + +def test_500_handler_unit(): + factory = RequestFactory() + request = factory.get("/") + + try: + raise Exception("forced") + except Exception as e: + response = response_for_exception(request, e) + + assert response.status_code == 500 + assert "Something went wrong on our side" in response.content.decode() diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index 79bdb20952..601565adba 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -12,21 +12,42 @@ def index(request): """ Render the home page of the Orange County Lettings site. - This view function handles requests to the root URL ('/'). - It returns a response using the 'index.html' template. + This view handles requests to the root URL ('/') and returns + the main landing page. - Args: - request (HttpRequest): The HTTP request object. - - Returns: - HttpResponse: Rendered home page template. + :param request: The HTTP request object. + :type request: HttpRequest + :return: Rendered home page template. + :rtype: HttpResponse """ return render(request, "index.html") def page_not_found(request, exception): + """ + Render the custom 404 error page. + + This view is used by Django when a requested URL does not exist. + + :param request: The HTTP request object. + :type request: HttpRequest + :param exception: The exception raised by the resolver. + :type exception: Exception + :return: Rendered 404 error page with HTTP status 404. + :rtype: HttpResponse + """ return render(request, "404.html", status=404) def server_error(request): + """ + Render the custom 500 error page. + + This view is used by Django when an unhandled server error occurs. + + :param request: The HTTP request object. + :type request: HttpRequest + :return: Rendered 500 error page with HTTP status 500. + :rtype: HttpResponse + """ return render(request, "500.html", status=500) diff --git a/profiles/tests.py b/profiles/tests.py index b6e3690363..98ae2c75bd 100644 --- a/profiles/tests.py +++ b/profiles/tests.py @@ -182,6 +182,7 @@ def test_profile_detail_nonexistent_returns_404(client): response = client.get(reverse("profiles:profile", args=["unknownuser"])) assert response.status_code == 404 + assert "Sorry, the page you are looking for does not exist." in response.content.decode() """ @@ -398,3 +399,4 @@ def test_profile_detail_user_without_profile_returns_404(client): response = client.get(url) assert response.status_code == 404 + assert "Sorry, the page you are looking for does not exist." in response.content.decode() From a05ced0e910ececa838b8914cebcc3f505fb9658 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 11 Apr 2026 11:37:26 +0200 Subject: [PATCH 044/113] Installed sentry-sdk for django, added keys in .env and updated requirements.txt --- .coverage | Bin 53248 -> 53248 bytes oc_lettings_site/settings.py | 18 +++++++++++++++++- requirements.txt | Bin 984 -> 1170 bytes 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.coverage b/.coverage index f087af94c5222e2f068528c39125618920f8e139..b0ff0ac9c04717cea4c1f408f319e3977e80a1b9 100644 GIT binary patch delta 74 zcmV-Q0JZ;spaX!Q1F!~wB$)sY^bh0@(GPkLa1UY+SPwrBC=VPD4i5wl_zvq1u@0vW gnX?fPZw`~2j;0M42m}cUG6DeLB|)kmvrLb)Kvp0YqyPW_ delta 72 zcmV-O0Jr~upaX!Q1F!~wB%1&a^bh0@(GPkLa1UY+SPwxDDi0nH5Dx|p`VQ?5vks{a eo3jxRaSoH2j;0G02m}cUE&>3c1SzvjkF-E3dlhg1 diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 8a10343108..ba3a71d76f 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -19,9 +19,15 @@ """ import os +from dotenv import load_dotenv +import sentry_sdk +from sentry_sdk.integrations.django import DjangoIntegration from pathlib import Path +# Load environment variables +load_dotenv() + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = Path(__file__).resolve().parent.parent @@ -29,13 +35,23 @@ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "fp$9^593hsriajg$_%=5trot9g!1qa@ew(o-1#@=&4%=hp46(s" +SECRET_KEY = os.getenv("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] +SENTRY_DSN = os.getenv("SENTRY_DSN") + +if SENTRY_DSN: + sentry_sdk.init( + dsn=SENTRY_DSN, + integrations=[DjangoIntegration()], + traces_sample_rate=1.0, + send_default_pii=True + ) + # Application definition INSTALLED_APPS = [ diff --git a/requirements.txt b/requirements.txt index 07c98a0cd9dbc01f1b53142c4607a3cf0058724f..4c53a754f258c713b345d8960ff4471cd487f275 100644 GIT binary patch delta 182 zcmcb?K8bUJn_?CPNw!+XA5xg8>kmG3WtF5H_7?-zb^FkPlRp%8H>b_H6L2Q;IIp%SRF7^pTIYMmZ~DT6ssrNLxZ c=E(}BKovPan8}dDU<_9Qa*i Date: Sat, 11 Apr 2026 11:47:01 +0200 Subject: [PATCH 045/113] Modified sentry settings --- oc_lettings_site/settings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index ba3a71d76f..351bee46e8 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -48,8 +48,7 @@ sentry_sdk.init( dsn=SENTRY_DSN, integrations=[DjangoIntegration()], - traces_sample_rate=1.0, - send_default_pii=True + traces_sample_rate=1.0 ) # Application definition From 32bfe4a275835ac44835915dfab19adc3376a51c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 11 Apr 2026 11:48:57 +0200 Subject: [PATCH 046/113] Added different levels of loggers --- oc_lettings_site/views.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index 601565adba..d08e8808f1 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -6,6 +6,9 @@ """ from django.shortcuts import render +import logging + +logger = logging.getLogger(__name__) def index(request): @@ -15,11 +18,15 @@ def index(request): This view handles requests to the root URL ('/') and returns the main landing page. + An info log is recorded to track normal application traffic. + :param request: The HTTP request object. :type request: HttpRequest :return: Rendered home page template. :rtype: HttpResponse """ + logger.info("Homepage accessed") + return render(request, "index.html") @@ -29,6 +36,8 @@ def page_not_found(request, exception): This view is used by Django when a requested URL does not exist. + A warning log is recorded to track invalid navigation attempts. + :param request: The HTTP request object. :type request: HttpRequest :param exception: The exception raised by the resolver. @@ -36,6 +45,7 @@ def page_not_found(request, exception): :return: Rendered 404 error page with HTTP status 404. :rtype: HttpResponse """ + logger.warning("404 error encountered", extra={"path": request.path, "method": request.method}) return render(request, "404.html", status=404) @@ -45,9 +55,13 @@ def server_error(request): This view is used by Django when an unhandled server error occurs. + An error log is recorded to capture critical failures for + monitoring and debugging purposes. + :param request: The HTTP request object. :type request: HttpRequest :return: Rendered 500 error page with HTTP status 500. :rtype: HttpResponse """ + logger.error("500 error encountered", extra={"path": request.path, "method": request.method}) return render(request, "500.html", status=500) From a36e6eb059e2ba4600826c1a154d7942aa94f2fd Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 11 Apr 2026 12:08:00 +0200 Subject: [PATCH 047/113] Added different levels of loggers for profiles.views --- profiles/views.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/profiles/views.py b/profiles/views.py index c198faa399..784ea700ca 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -10,6 +10,10 @@ from django.shortcuts import render, get_object_or_404 from .models import Profile +import logging + +logger = logging.getLogger(__name__) + def profiles_index(request): """ @@ -19,12 +23,22 @@ def profiles_index(request): to the 'profiles/index.html' template under the context variable 'profiles_list'. + This view logs access events for monitoring purposes. + :param request: The HTTP request object. :type request: HttpRequest :return: Rendered HTML page with the list of profiles. :rtype: HttpResponse """ + + logger.info( + "Profiles list accessed", + extra={ + "path": request.path, + "method": request.method, + }, + ) profiles_list = Profile.objects.all() context = {"profiles_list": profiles_list} return render(request, "profiles/index.html", context) @@ -38,6 +52,8 @@ def profile(request, username): to the provided username and passes it to the 'profiles/profile.html' template under the context variable 'profile'. + This view logs access events for monitoring purposes. + :param request: The HTTP request object. :type request: HttpRequest :param username: The username of the user whose profile is requested. @@ -48,6 +64,15 @@ def profile(request, username): :raises Http404: If no profile matches the given username. """ + + logger.info( + "Profile detail accessed", + extra={ + "username": username, + "path": request.path, + "method": request.method, + }, + ) profile = get_object_or_404(Profile, user__username=username) context = {"profile": profile} return render(request, "profiles/profile.html", context) From 9da3ccc170ff8839fe9aa4103103540293861696 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 13 Apr 2026 11:17:25 +0200 Subject: [PATCH 048/113] Added different levels of loggers for lettings.views Tested sentry in debug = False conditions, all seems to be working properly at this point. --- lettings/views.py | 24 +++++++++++++++++++++++- oc_lettings_site/settings.py | 17 ++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/lettings/views.py b/lettings/views.py index b5400811d3..694ef3e12c 100644 --- a/lettings/views.py +++ b/lettings/views.py @@ -12,6 +12,10 @@ from django.http import HttpRequest, HttpResponse from .models import Letting +import logging + +logger = logging.getLogger(__name__) + def lettings_index(request: HttpRequest) -> HttpResponse: """ @@ -20,11 +24,20 @@ def lettings_index(request: HttpRequest) -> HttpResponse: This view retrieves all Letting instances from the database and renders them using the ``lettings/index.html`` template. + This view logs access attempts for monitoring purposes. + :param request: The HTTP request object. :type request: HttpRequest :return: Rendered HTML page displaying the list of lettings. :rtype: HttpResponse """ + logger.info( + "Lettings list accessed", + extra={ + "path": request.path, + "method": request.method, + }, + ) lettings_list = Letting.objects.all() context = {"lettings_list": lettings_list} return render(request, "lettings/index.html", context) @@ -38,15 +51,24 @@ def letting(request: HttpRequest, letting_id: int) -> HttpResponse: primary key and renders its details using the ``lettings/letting.html`` template. + This view logs access attempts for monitoring purposes. + :param request: The HTTP request object. :type request: HttpRequest :param letting_id: The unique identifier of the letting. :type letting_id: int :return: Rendered HTML page displaying the letting details. :rtype: HttpResponse - :raises Letting.DoesNotExist: If no letting matches the given ID. :raises Http404: If no letting matches the given ID. """ + logger.info( + "Letting detail accessed", + extra={ + "letting_id": letting_id, + "path": request.path, + "method": request.method, + }, + ) letting = get_object_or_404(Letting, id=letting_id) context = { "title": letting.title, diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 351bee46e8..66bb3d6451 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -22,6 +22,9 @@ from dotenv import load_dotenv import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration +from sentry_sdk.integrations.logging import LoggingIntegration + +import logging from pathlib import Path @@ -38,17 +41,21 @@ SECRET_KEY = os.getenv("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = False -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ["localhost", "127.0.0.1"] SENTRY_DSN = os.getenv("SENTRY_DSN") if SENTRY_DSN: + + sentry_logging = LoggingIntegration( + level=logging.INFO, + event_level=logging.ERROR, + ) + sentry_sdk.init( - dsn=SENTRY_DSN, - integrations=[DjangoIntegration()], - traces_sample_rate=1.0 + dsn=SENTRY_DSN, integrations=[DjangoIntegration(), sentry_logging], traces_sample_rate=1.0 ) # Application definition From 42ba56a569ca47b896053937d2e54255fba26493 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 15 Apr 2026 09:15:13 +0200 Subject: [PATCH 049/113] Update coverage --- .coverage | Bin 53248 -> 53248 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.coverage b/.coverage index b0ff0ac9c04717cea4c1f408f319e3977e80a1b9..6cab487c88a5973ba8e8834de0e7eee3897a8f10 100644 GIT binary patch delta 153 zcmZozz}&Eac>`O6$^-`fFZ@sVFY;IM7x2gPhw@wVtME(kbMXD+`^5K@Z#CaSz6qNJ z1=9Eg`2|@RITiSr*03=An8U#Ep?LE0zH|XTZlI_fH>>EBD$Yl;)$jreGR4&BCG@+574P+cA(+(kq8b1aGd;QG?{SzDj DQhp{y delta 144 zcmZozz}&Eac>`O6%1j3SFZ@sVFY;IM7w{+XNATP8Yw*kP^YOFr{os4ew~=o--^|T| z0{MJ=Jc2BYoKk#DJ`4p+3>K3Y_NDXjaD#-nnK&C*8dwx2OY|!nigU1VavCu)JWzF9 sCC{YL@Q>jT^LE~lRz41Ppjsg&rU{KaY#?2nOcsnlgZ6AL=%3&K05hi{&;S4c From 548951b9524d472116617d44c6a55182ce35ddb4 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 27 Apr 2026 19:07:43 +0200 Subject: [PATCH 050/113] Working on docker and CI pipeline, currently runnin container on local, with statics loaded, but debug= True at the moment --- .dockerignore | 7 +++++++ Dockerfile | 23 +++++++++++++++++++++++ oc_lettings_site/settings.py | 6 ++++-- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..564b105d7c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.env +.git +.gitignore +__pycache__/ +*.pyc +*.pyo +*.pyd \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..a74f43c695 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# Base image +FROM python:3.10-slim + +# Prevent Python from buffering stdout/stderr +ENV PYTHONUNBUFFERED=1 + +# Set work directory +WORKDIR /app + +# Copy requirements first (cache optimization) +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy project +COPY . . + +# Expose port +EXPOSE 8000 + +# Run server +CMD ["sh", "-c", "python manage.py collectstatic --noinput && python manage.py runserver 0.0.0.0:8000"] \ No newline at end of file diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 66bb3d6451..4dd72a5681 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -41,7 +41,7 @@ SECRET_KEY = os.getenv("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False +DEBUG = True ALLOWED_HOSTS = ["localhost", "127.0.0.1"] @@ -146,9 +146,11 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") +BASE_DIR = Path(__file__).resolve().parent.parent STATIC_URL = "/static/" +STATIC_ROOT = BASE_DIR / "staticfiles" + STATICFILES_DIRS = [ BASE_DIR / "static", ] From 1b81b20bdf73bcdcf874fcfdcd3b69beb4997626 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 5 May 2026 10:47:56 +0200 Subject: [PATCH 051/113] Updated architecture with gunicorn to get ready for production --- .coverage | Bin 53248 -> 53248 bytes Dockerfile | 2 +- requirements.txt | Bin 1170 -> 1206 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.coverage b/.coverage index 6cab487c88a5973ba8e8834de0e7eee3897a8f10..4d0a77c49a54dc3aed2173e0166840c490885563 100644 GIT binary patch delta 16 XcmZozz}&Eac|%A)Bk$(W{?!fuHgX1P delta 16 XcmZozz}&Eac|%A)BlG6a{?!fuHZ}%e diff --git a/Dockerfile b/Dockerfile index a74f43c695..dd961323de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,4 @@ COPY . . EXPOSE 8000 # Run server -CMD ["sh", "-c", "python manage.py collectstatic --noinput && python manage.py runserver 0.0.0.0:8000"] \ No newline at end of file +CMD ["gunicorn", "oc_lettings_site.wsgi:application", "--bind", "0.0.0.0:8000"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4c53a754f258c713b345d8960ff4471cd487f275..2ef30704963b263edb1eac7a8145173c904f3303 100644 GIT binary patch delta 44 ucmbQlxs7u}8KZnULn%WZLncEqLq0QdJG02yt$B3kr@E##t7{I delta 12 TcmdnSIf-*a8RO Date: Mon, 11 May 2026 10:35:14 +0200 Subject: [PATCH 052/113] First test for compilation part of the pipeline --- .github/workflows/ci.yml | 37 ++++++++++++++++++++++++++++++++++++ oc_lettings_site/settings.py | 4 ++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..b74eef18db --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + test: + + runs-on: ubuntu-latest + + steps: + # 1. Checkout + - name: Checkout repository + uses: actions/checkout@v4 + + # 2. Install Python + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + # 3. Install dependencies + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + # 4. Lint (flake8) + - name: Run flake8 + run: flake8 . + + # 5. Tests + - name: Run tests + run: pytest \ No newline at end of file diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 4dd72a5681..bc462e6e05 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -41,9 +41,9 @@ SECRET_KEY = os.getenv("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = os.getenv("DEBUG", "False") == "True" -ALLOWED_HOSTS = ["localhost", "127.0.0.1"] +ALLOWED_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0"] SENTRY_DSN = os.getenv("SENTRY_DSN") From 28bd519b4925a99b7649679877d1b197472dbf14 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 11 May 2026 10:41:52 +0200 Subject: [PATCH 053/113] Second test for compilation part of the pipeline --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index bc462e6e05..19ad88cbc2 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -43,7 +43,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.getenv("DEBUG", "False") == "True" -ALLOWED_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0"] +ALLOWED_HOSTS = ["localhost", "127.0.0.1"] SENTRY_DSN = os.getenv("SENTRY_DSN") From 30e0647711341e1d6756a60f68718c3f06703691 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 11 May 2026 10:45:53 +0200 Subject: [PATCH 054/113] Second test for compilation part of the pipeline --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b74eef18db..0a6736a1c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,7 @@ name: CI on: push: - branches: [ main, master ] pull_request: - branches: [ main, master ] jobs: test: From c364d50abf86e70fca082d31a152d417f6bb8eec Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 11 May 2026 11:10:23 +0200 Subject: [PATCH 055/113] Third test for compilation part of the pipeline with secret written on github --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a6736a1c2..67b16e0616 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ jobs: runs-on: ubuntu-latest + env: + SECRET_KEY: ${{ secrets.SECRET_KEY }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + steps: # 1. Checkout - name: Checkout repository From 7a99935f397c0f1a67bd9f3fc016395089bd1a99 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 11:56:33 +0200 Subject: [PATCH 056/113] Added coverage check for validating CI --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67b16e0616..c3486457d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,6 @@ jobs: - name: Run flake8 run: flake8 . - # 5. Tests - - name: Run tests - run: pytest \ No newline at end of file + # 5. Tests + coverage + - name: Run tests with coverage + run: pytest --cov=. --cov-report=term-missing --cov-fail-under=80 \ No newline at end of file From ade31a14816436d5a9c916bffa14876cd7859848 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 11:59:56 +0200 Subject: [PATCH 057/113] Added coverage check for validating CI --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 19ad88cbc2..dd31d7496a 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -153,4 +153,4 @@ STATICFILES_DIRS = [ BASE_DIR / "static", -] +] \ No newline at end of file From 4d0aaa709ffc8ea54dd38515a8a97fa53dd3a6a4 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 12:00:45 +0200 Subject: [PATCH 058/113] Test incorrect linting for CI --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index dd31d7496a..19ad88cbc2 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -153,4 +153,4 @@ STATICFILES_DIRS = [ BASE_DIR / "static", -] \ No newline at end of file +] From b972d66f9c6d221cf5f3d0061aafdf691cf4ed8a Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 12:02:15 +0200 Subject: [PATCH 059/113] Test incorrect linting for CI --- oc_lettings_site/settings.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 19ad88cbc2..fbb12d7699 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -117,10 +117,7 @@ AUTH_PASSWORD_VALIDATORS = [ { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",},{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", From f1783afc47900cccff942b4568342ebc0d41b8db Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 12:03:15 +0200 Subject: [PATCH 060/113] Roll back to proper linting --- oc_lettings_site/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index fbb12d7699..19ad88cbc2 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -117,7 +117,10 @@ AUTH_PASSWORD_VALIDATORS = [ { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",},{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", From 5e97b6efbd0eaf5007a9f4e0472b6645fcf89966 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 12:59:15 +0200 Subject: [PATCH 061/113] Renamed ci.yml --- .github/workflows/{ci.yml => compile.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{ci.yml => compile.yml} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/compile.yml similarity index 100% rename from .github/workflows/ci.yml rename to .github/workflows/compile.yml From a349aca254ed5c56d8a1b93717237e0cea4be5a0 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 13:28:37 +0200 Subject: [PATCH 062/113] Renamed ci.yml --- .github/workflows/{compile.yml => pipeline.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{compile.yml => pipeline.yml} (100%) diff --git a/.github/workflows/compile.yml b/.github/workflows/pipeline.yml similarity index 100% rename from .github/workflows/compile.yml rename to .github/workflows/pipeline.yml From e871343d68c48c1f91026d1da9026e2e5bff1a4b Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 12 May 2026 17:07:44 +0200 Subject: [PATCH 063/113] Renamed ci.yml --- .github/workflows/pipeline.yml | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index c3486457d4..67fad00ee7 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -1,11 +1,11 @@ -name: CI +name: Pipeline on: push: pull_request: jobs: - test: + compile: runs-on: ubuntu-latest @@ -36,4 +36,32 @@ jobs: # 5. Tests + coverage - name: Run tests with coverage - run: pytest --cov=. --cov-report=term-missing --cov-fail-under=80 \ No newline at end of file + run: pytest --cov=. --cov-report=term-missing --cov-fail-under=80 + + + containerize: + + needs: compile + + if: github.ref == 'refs/heads/master' + + runs-on: ubuntu-latest + + steps: + # 1. Checkout repository + - name: Checkout repository + uses: actions/checkout@v4 + + # 2. Login Docker Hub + - name: Login to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + + # 3. Build Docker image + - name: Build Docker image + run: | + docker build -t ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:${{ github.sha }} . + + # 4. Push image to Docker Hub + - name: Push Docker image + run: | + docker push ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:${{ github.sha }} From 5a3c2fe81cdf708dd74d36c710831b73928132ac Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Thu, 14 May 2026 20:02:09 +0200 Subject: [PATCH 064/113] Added postgresql driver for django on requirements.txt --- requirements.txt | Bin 1206 -> 1256 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2ef30704963b263edb1eac7a8145173c904f3303..e5f8865f94fafcb720a589e09504fa1e8eabaa75 100644 GIT binary patch delta 56 zcmdnS`GRx9EGF$@hDwHHhJ1zshI9ra23>|EhD?S$hD3%Uptvm%8ZqcGSOT#jgVE;6 HOv+3EgRu*; delta 12 TcmaFCxs7wfET+w8m}Hm$BW(nn From 151be2e49bd30abc83be1c2d4f6645271c7be0ce Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 15 May 2026 15:05:35 +0200 Subject: [PATCH 065/113] Test pipeline --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 19ad88cbc2..65dca19645 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -112,7 +112,7 @@ } } -# Password validation +# Password validations # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ From ad7d38ebcf3decd589de186fa0288bb2829284c7 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 22 May 2026 17:23:31 +0200 Subject: [PATCH 066/113] Updated postgre stack, migration succeeded, database not filled yet. --- docker-compose.yml | 25 +++++++++++++++++++++++++ oc_lettings_site/settings.py | 15 +++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..050b3f31c7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + + db: + image: postgres:16 + container_name: oc_lettings_postgres + env_file: + - .env + volumes: + - oc_lettings_postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + + web: + build: . + container_name: oc_lettings_web + env_file: + - .env + ports: + - "8000:8000" + depends_on: + - db + command: gunicorn oc_lettings_site.wsgi:application --bind 0.0.0.0:8000 + +volumes: + oc_lettings_postgres_data: \ No newline at end of file diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 65dca19645..7ced2869b8 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -107,8 +107,15 @@ DATABASES = { "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "oc-lettings-site.sqlite3"), + "ENGINE": "django.db.backends.postgresql", + "NAME": os.getenv("POSTGRES_DB"), + "USER": os.getenv("POSTGRES_USER"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD"), + "HOST": os.getenv("DB_HOST"), + "PORT": os.getenv("DB_PORT"), + "OPTIONS": { + "options": "-c timezone=UTC" + }, } } @@ -135,13 +142,13 @@ LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" +TIME_ZONE = "Etc/UTC" USE_I18N = True USE_L10N = True -USE_TZ = True +USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ From 35982b0e8bef42661071fb7bd48e4a380d96f122 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 25 May 2026 11:44:19 +0200 Subject: [PATCH 067/113] Database is now populated, site is running, and datas seams to be clean by now --- oc_lettings_site/settings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 7ced2869b8..c3c5465deb 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -113,9 +113,7 @@ "PASSWORD": os.getenv("POSTGRES_PASSWORD"), "HOST": os.getenv("DB_HOST"), "PORT": os.getenv("DB_PORT"), - "OPTIONS": { - "options": "-c timezone=UTC" - }, + "OPTIONS": {"options": "-c timezone=UTC"}, } } From 4c25ca382fb7a7f10fb9e663feacbc0456254d94 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 29 May 2026 11:00:10 +0200 Subject: [PATCH 068/113] Test, using sqlite db for CI tests --- .github/workflows/pipeline.yml | 1 + oc_lettings_site/settings.py | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 67fad00ee7..24a1cce3db 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -12,6 +12,7 @@ jobs: env: SECRET_KEY: ${{ secrets.SECRET_KEY }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + USE_SQLITE: "True" steps: # 1. Checkout diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index c3c5465deb..cf3d93a4cd 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -105,17 +105,26 @@ # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql", - "NAME": os.getenv("POSTGRES_DB"), - "USER": os.getenv("POSTGRES_USER"), - "PASSWORD": os.getenv("POSTGRES_PASSWORD"), - "HOST": os.getenv("DB_HOST"), - "PORT": os.getenv("DB_PORT"), - "OPTIONS": {"options": "-c timezone=UTC"}, +USE_SQLITE = os.getenv("USE_SQLITE", "False") == "True" + +if USE_SQLITE: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } + } +else: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.getenv("POSTGRES_DB"), + "USER": os.getenv("POSTGRES_USER"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD"), + "HOST": os.getenv("DB_HOST"), + "PORT": os.getenv("DB_PORT"), + } } -} # Password validations # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators From 78855455adfc7650873c784e5d48cd8923e29a61 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 29 May 2026 11:12:58 +0200 Subject: [PATCH 069/113] Test, using sqlite db for CI tests --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 24a1cce3db..07f8f7918c 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -12,7 +12,7 @@ jobs: env: SECRET_KEY: ${{ secrets.SECRET_KEY }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - USE_SQLITE: "True" + USE_SQLITE: True steps: # 1. Checkout From 30ada86a06853e48473d5a2f61e03cc420cc3af6 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 29 May 2026 11:23:36 +0200 Subject: [PATCH 070/113] Test, using sqlite db for CI tests --- .github/workflows/pipeline.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 07f8f7918c..690e787ad6 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -35,6 +35,11 @@ jobs: - name: Run flake8 run: flake8 . + # 5. Debug environment + - name: Debug database configuration + run: | + echo "USE_SQLITE=$USE_SQLITE" + # 5. Tests + coverage - name: Run tests with coverage run: pytest --cov=. --cov-report=term-missing --cov-fail-under=80 From a51fa107f0cdbfe6d4d69d3e57bfc7299573ed62 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 1 Jun 2026 06:37:57 +0200 Subject: [PATCH 071/113] Test, using sqlite db for CI tests --- .github/workflows/pipeline.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 690e787ad6..9f98a807a6 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -39,6 +39,8 @@ jobs: - name: Debug database configuration run: | echo "USE_SQLITE=$USE_SQLITE" + python -c "import os; print('USE_SQLITE=', os.getenv('USE_SQLITE'))" + python manage.py shell -c "from django.conf import settings; print(settings.DATABASES)" # 5. Tests + coverage - name: Run tests with coverage From 54f8b877ec7c3475417869bafc7db39a5d88b272 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 1 Jun 2026 06:46:22 +0200 Subject: [PATCH 072/113] Test, using sqlite db for CI tests --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 9f98a807a6..d5b8ad6c09 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -12,7 +12,7 @@ jobs: env: SECRET_KEY: ${{ secrets.SECRET_KEY }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - USE_SQLITE: True + USE_SQLITE: "True" steps: # 1. Checkout From 916309698df17e80001e9c39bd77c34c1cdfdc36 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 1 Jun 2026 06:49:46 +0200 Subject: [PATCH 073/113] Test, using sqlite db for CI tests --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index cf3d93a4cd..cb052d8831 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -105,7 +105,7 @@ # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -USE_SQLITE = os.getenv("USE_SQLITE", "False") == "True" +USE_SQLITE = os.getenv("USE_SQLITE", "").strip().lower() == "true" if USE_SQLITE: DATABASES = { From 3f6e177ec2aade56b55e05bd74ddd973333f73fa Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 1 Jun 2026 06:51:36 +0200 Subject: [PATCH 074/113] Test, using sqlite db for CI tests --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index cb052d8831..fa56761ac8 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -111,7 +111,7 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "NAME": str(BASE_DIR / "db.sqlite3"), } } else: From d422e936d75f3b4e8d9f643feb799fa7214f91cb Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 1 Jun 2026 06:55:41 +0200 Subject: [PATCH 075/113] Removed debugging prints, and updated gitignore. CI is now running on sqlite db for tests. --- .github/workflows/pipeline.yml | 7 ------- .gitignore | 3 ++- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index d5b8ad6c09..24a1cce3db 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -35,13 +35,6 @@ jobs: - name: Run flake8 run: flake8 . - # 5. Debug environment - - name: Debug database configuration - run: | - echo "USE_SQLITE=$USE_SQLITE" - python -c "import os; print('USE_SQLITE=', os.getenv('USE_SQLITE'))" - python manage.py shell -c "from django.conf import settings; print(settings.DATABASES)" - # 5. Tests + coverage - name: Run tests with coverage run: pytest --cov=. --cov-report=term-missing --cov-fail-under=80 diff --git a/.gitignore b/.gitignore index 68500e8a60..04ea67a31a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ venv .idea/ .env -*.log \ No newline at end of file +*.log +db.sqlite3 \ No newline at end of file From e2c5769ef1ffd3f212b80939cf087314fe42d602 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 3 Jun 2026 07:41:37 +0200 Subject: [PATCH 076/113] Resolved static service using whitenoise and appropriate settings. App si now running on DEBUG = False, and style is conform. Admin and site tested manually. --- Dockerfile | 5 +++++ oc_lettings_site/settings.py | 7 +++++-- requirements.txt | Bin 1256 -> 1296 bytes 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index dd961323de..d524c5a178 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,11 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy project COPY . . +# Collect statics +RUN SECRET_KEY=dummy-secret-key \ + USE_SQLITE=true \ + python manage.py collectstatic --noinput + # Expose port EXPOSE 8000 diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index fa56761ac8..0667bf3382 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -74,6 +74,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -160,11 +161,13 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -BASE_DIR = Path(__file__).resolve().parent.parent - STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "staticfiles" STATICFILES_DIRS = [ BASE_DIR / "static", ] + +STATICFILES_STORAGE = ( + "whitenoise.storage.CompressedStaticFilesStorage" +) diff --git a/requirements.txt b/requirements.txt index e5f8865f94fafcb720a589e09504fa1e8eabaa75..24772fb839cd694f2ff37d38fdbc4c4c34221b55 100644 GIT binary patch delta 48 ycmaFCIe}}#3l@!Xh75*Gh7yKUhCGIRAgdV2vIRmj20aEt1|uM8z`)DE#Q*>=RtZG_ delta 7 OcmbQh^@4N53l;zj!viP) From 00a51ee6495ade54d68836228f57a7f1b4ae9b3c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 7 Jun 2026 10:25:37 +0200 Subject: [PATCH 077/113] Updated settings.py doctring --- oc_lettings_site/settings.py | 38 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 0667bf3382..88deef1480 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -1,21 +1,35 @@ """ Django settings for the oc_lettings_site project. -This configuration file defines the core settings used to run -the application in a development environment. +This configuration file defines the settings used to run the application +across development, testing and production environments. -It includes: +Main features include: -- Application registration -- Middleware configuration -- Template settings -- Database configuration (SQLite) -- Authentication and password validation rules -- Internationalization settings -- Static file management +Django application registration +Middleware configuration +Template configuration +Environment-based database selection (SQLite or PostgreSQL) +Authentication and password validation +Internationalization settings +Static files management with WhiteNoise +Error monitoring with Sentry +Environment variable configuration through .env files -These settings are intended for development purposes and -are not optimized for production deployment. +Database strategy: + +SQLite is used for local development and CI testing when +USE_SQLITE is enabled. +PostgreSQL is used for containerized and production deployments. + +Static assets are collected during the Docker image build process +and served through WhiteNoise. + +Sensitive settings such as credentials, secret keys and deployment +configuration are injected through environment variables. + +This file is designed to support both local development and +production-ready deployments. """ import os From ccaa015d091d8a74454d5e5568567288d6c3e60c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 12 Jun 2026 17:22:56 +0200 Subject: [PATCH 078/113] Resolved quick start problem. USE_SQLITE was pointing at a wrong file. --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 88deef1480..bef494f16e 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -126,7 +126,7 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": str(BASE_DIR / "db.sqlite3"), + "NAME": str(BASE_DIR / "oc-lettings-site.sqlite3"), } } else: From 1e77e1fbaeac38a05b74d57c077be268b2d4468f Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 12 Jun 2026 17:38:56 +0200 Subject: [PATCH 079/113] Update pipeline.yml added a second tag "latest" on the image pushed after containerize job. --- .github/workflows/pipeline.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 24a1cce3db..e115f85416 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -57,12 +57,17 @@ jobs: - name: Login to Docker Hub run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - # 3. Build Docker image + # 3. Build Docker image (SHA + latest) - name: Build Docker image run: | docker build -t ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:${{ github.sha }} . + docker tag ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:latest - # 4. Push image to Docker Hub - - name: Push Docker image + # 4. Push Docker images + - name: Push Docker image (SHA) run: | docker push ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:${{ github.sha }} + + - name: Push Docker image (latest) + run: | + docker push ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:latest From c0acba50aa82b0030370a31f362db378e36cdb58 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 15 Jun 2026 12:29:13 +0200 Subject: [PATCH 080/113] Moved port:8000 into an environement variable to get more adaptability for render --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d524c5a178..2da81ab666 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,4 +25,4 @@ RUN SECRET_KEY=dummy-secret-key \ EXPOSE 8000 # Run server -CMD ["gunicorn", "oc_lettings_site.wsgi:application", "--bind", "0.0.0.0:8000"] \ No newline at end of file +CMD gunicorn oc_lettings_site.wsgi:application --bind 0.0.0.0:$PORT \ No newline at end of file From 1ccd151b55ea8797587c8a296554cb21d0f3b15f Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 15 Jun 2026 13:30:54 +0200 Subject: [PATCH 081/113] Updated ALLOWED_HOSTS for render --- oc_lettings_site/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index bef494f16e..2a982a01fc 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -57,7 +57,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.getenv("DEBUG", "False") == "True" -ALLOWED_HOSTS = ["localhost", "127.0.0.1"] +ALLOWED_HOSTS = ["localhost", "127.0.0.1", ".onrender.com"] SENTRY_DSN = os.getenv("SENTRY_DSN") From 093b096d516b29bb9a842269901a61b8523808b4 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 21 Jun 2026 12:26:15 +0200 Subject: [PATCH 082/113] Creating a starting script to automate migrations and data loading --- Dockerfile | 6 ++++-- start.sh | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 start.sh diff --git a/Dockerfile b/Dockerfile index 2da81ab666..8312360b40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,5 +24,7 @@ RUN SECRET_KEY=dummy-secret-key \ # Expose port EXPOSE 8000 -# Run server -CMD gunicorn oc_lettings_site.wsgi:application --bind 0.0.0.0:$PORT \ No newline at end of file +# Run server with start.sh script +RUN chmod +x start.sh + +CMD ["./start.sh"] diff --git a/start.sh b/start.sh new file mode 100644 index 0000000000..364ef5333d --- /dev/null +++ b/start.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +echo "Applying migrations..." +python manage.py migrate --noinput + +echo "Starting Gunicorn..." +exec gunicorn oc_lettings_site.wsgi:application \ + --bind 0.0.0.0:${PORT:-8000} \ No newline at end of file From 38b1c9eb465e1c627aa12736958f506c503ccede Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 21 Jun 2026 13:08:11 +0200 Subject: [PATCH 083/113] Created a datajson to initialize datas in render postgre service, and a command in starting script to load it --- data.json | 585 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ start.sh | 3 + 2 files changed, 588 insertions(+) create mode 100644 data.json diff --git a/data.json b/data.json new file mode 100644 index 0000000000..b7c80eae1d --- /dev/null +++ b/data.json @@ -0,0 +1,585 @@ +[ +{ + "model": "lettings.address", + "pk": 1, + "fields": { + "number": 7217, + "street": "Bedford Street", + "city": "Brunswick", + "state": "GA", + "zip_code": 31525, + "country_iso_code": "USA" + } +}, +{ + "model": "lettings.address", + "pk": 2, + "fields": { + "number": 4, + "street": "Military Street", + "city": "Willoughby", + "state": "OH", + "zip_code": 44094, + "country_iso_code": "USA" + } +}, +{ + "model": "lettings.address", + "pk": 3, + "fields": { + "number": 340, + "street": "Wintergreen Avenue", + "city": "Newport News", + "state": "VA", + "zip_code": 23601, + "country_iso_code": "USA" + } +}, +{ + "model": "lettings.address", + "pk": 4, + "fields": { + "number": 9230, + "street": "E. Joy Ridge Street", + "city": "Marquette", + "state": "MI", + "zip_code": 49855, + "country_iso_code": "USA" + } +}, +{ + "model": "lettings.address", + "pk": 5, + "fields": { + "number": 9606, + "street": "Harvard Street", + "city": "Aliquippa", + "state": "PA", + "zip_code": 15001, + "country_iso_code": "USA" + } +}, +{ + "model": "lettings.address", + "pk": 6, + "fields": { + "number": 588, + "street": "Argyle Avenue", + "city": "East Meadow", + "state": "NY", + "zip_code": 11554, + "country_iso_code": "USA" + } +}, +{ + "model": "lettings.letting", + "pk": 1, + "fields": { + "title": "Joshua Tree Green Haus /w Hot Tub", + "address": 1 + } +}, +{ + "model": "lettings.letting", + "pk": 2, + "fields": { + "title": "Oceanview Retreat", + "address": 2 + } +}, +{ + "model": "lettings.letting", + "pk": 3, + "fields": { + "title": "'Silo Studio' Cottage", + "address": 3 + } +}, +{ + "model": "lettings.letting", + "pk": 4, + "fields": { + "title": "Pirates of the Caribbean Getaway", + "address": 4 + } +}, +{ + "model": "lettings.letting", + "pk": 5, + "fields": { + "title": "The Mushroom Dome Retreat & LAND of Paradise Suite", + "address": 5 + } +}, +{ + "model": "lettings.letting", + "pk": 6, + "fields": { + "title": "Underground Hygge", + "address": 6 + } +}, +{ + "model": "auth.user", + "pk": 1, + "fields": { + "password": "pbkdf2_sha256$180000$p35UH2yYInHF$4LTphT2basUmvfLq+XHETVvKONZrWUsLfKu1K8/Hqdk=", + "last_login": "2026-04-04T12:22:01.195", + "is_superuser": true, + "username": "admin", + "first_name": "", + "last_name": "", + "email": "admin@email.com", + "is_staff": true, + "is_active": true, + "date_joined": "2020-06-14T09:41:15.326", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "auth.user", + "pk": 2, + "fields": { + "password": "pbkdf2_sha256$180000$8ZKjEEdeYubZ$jq4T/Vaa2DWdAvzNys4ynNO6Wd/PsWe3dux20F7BGgQ=", + "last_login": null, + "is_superuser": false, + "username": "4meRomance", + "first_name": "John", + "last_name": "Rodriguez", + "email": "coemperor@famemma.net", + "is_staff": false, + "is_active": true, + "date_joined": "2020-06-14T09:44:05", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "auth.user", + "pk": 3, + "fields": { + "password": "pbkdf2_sha256$180000$DdNkE39rolFF$nGmWZanXv4GlcTxtfUgc+MUIqBgDszAtvFfuFu538LQ=", + "last_login": null, + "is_superuser": false, + "username": "AirWow", + "first_name": "Ada", + "last_name": "Paul", + "email": "flocation.vam4@glendenningflowerdesign.com", + "is_staff": false, + "is_active": true, + "date_joined": "2020-06-14T09:44:45", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "auth.user", + "pk": 4, + "fields": { + "password": "pbkdf2_sha256$180000$3VJdHtu39cbD$8qNVkvJ0KddsvfFueEm09Sg0LxgFievigmtAEb39paE=", + "last_login": null, + "is_superuser": false, + "username": "DavWin", + "first_name": "Cassandra", + "last_name": "Grahm", + "email": "5houssam.kessaiso@facpidif.ml", + "is_staff": false, + "is_active": true, + "date_joined": "2020-06-14T09:46:28", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "auth.user", + "pk": 5, + "fields": { + "password": "pbkdf2_sha256$180000$zjnQu4LiqMAT$Qxom08ahzw11iPlX6kYyySa94yJXdjrptta6Qzx8HWE=", + "last_login": null, + "is_superuser": false, + "username": "HeadlinesGazer", + "first_name": "Jamie", + "last_name": "Lal", + "email": "jssssss33@acee9.live", + "is_staff": false, + "is_active": true, + "date_joined": "2020-06-14T09:47:21", + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "sessions.session", + "pk": "6rmrxn3uts8m2ytfny39msvhhcr7x6ge", + "fields": { + "session_data": "YzFjNTIxZWFkZGRkMmE2Y2MwNDY2YWUzOTg1MGJlNTM1MTRiYjliNjp7Il9hdXRoX3VzZXJfaWQiOiIxIiwiX2F1dGhfdXNlcl9iYWNrZW5kIjoiZGphbmdvLmNvbnRyaWIuYXV0aC5iYWNrZW5kcy5Nb2RlbEJhY2tlbmQiLCJfYXV0aF91c2VyX2hhc2giOiI1NTMxMjkyZWYxMzBjMjBjNDMwOWE5YzEzMGJmMDYyZjlmN2Q0NTQxIn0=", + "expire_date": "2020-06-28T09:41:56.984" + } +}, +{ + "model": "sessions.session", + "pk": "r8q5klofqavm3xgkrokfb2k0ftuqg5qi", + "fields": { + "session_data": "YzFjNTIxZWFkZGRkMmE2Y2MwNDY2YWUzOTg1MGJlNTM1MTRiYjliNjp7Il9hdXRoX3VzZXJfaWQiOiIxIiwiX2F1dGhfdXNlcl9iYWNrZW5kIjoiZGphbmdvLmNvbnRyaWIuYXV0aC5iYWNrZW5kcy5Nb2RlbEJhY2tlbmQiLCJfYXV0aF91c2VyX2hhc2giOiI1NTMxMjkyZWYxMzBjMjBjNDMwOWE5YzEzMGJmMDYyZjlmN2Q0NTQxIn0=", + "expire_date": "2026-04-18T12:22:01.200" + } +}, +{ + "model": "admin.logentry", + "pk": 1, + "fields": { + "action_time": "2020-06-14T09:44:05.888", + "user": 1, + "content_type": 7, + "object_id": "2", + "object_repr": "4meRomance", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 2, + "fields": { + "action_time": "2020-06-14T09:44:45.294", + "user": 1, + "content_type": 7, + "object_id": "3", + "object_repr": "AirWow", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 3, + "fields": { + "action_time": "2020-06-14T09:45:14.363", + "user": 1, + "content_type": 7, + "object_id": "3", + "object_repr": "AirWow", + "action_flag": 2, + "change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\", \"Email address\"]}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 4, + "fields": { + "action_time": "2020-06-14T09:45:57.716", + "user": 1, + "content_type": 7, + "object_id": "2", + "object_repr": "4meRomance", + "action_flag": 2, + "change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\", \"Email address\"]}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 5, + "fields": { + "action_time": "2020-06-14T09:46:29.087", + "user": 1, + "content_type": 7, + "object_id": "4", + "object_repr": "DavWin", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 6, + "fields": { + "action_time": "2020-06-14T09:46:46.028", + "user": 1, + "content_type": 7, + "object_id": "4", + "object_repr": "DavWin", + "action_flag": 2, + "change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\", \"Email address\"]}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 7, + "fields": { + "action_time": "2020-06-14T09:47:21.157", + "user": 1, + "content_type": 7, + "object_id": "5", + "object_repr": "HeadlinesGazer", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 8, + "fields": { + "action_time": "2020-06-14T09:47:39.497", + "user": 1, + "content_type": 7, + "object_id": "5", + "object_repr": "HeadlinesGazer", + "action_flag": 2, + "change_message": "[{\"changed\": {\"fields\": [\"First name\", \"Last name\", \"Email address\"]}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 9, + "fields": { + "action_time": "2020-06-14T09:47:42.559", + "user": 1, + "content_type": 7, + "object_id": "5", + "object_repr": "HeadlinesGazer", + "action_flag": 2, + "change_message": "[]" + } +}, +{ + "model": "admin.logentry", + "pk": 10, + "fields": { + "action_time": "2020-06-14T09:49:23.116", + "user": 1, + "content_type": 1, + "object_id": "1", + "object_repr": "7217 Bedford Street", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 11, + "fields": { + "action_time": "2020-06-14T09:49:50.934", + "user": 1, + "content_type": 1, + "object_id": "2", + "object_repr": "4 Military Street", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 12, + "fields": { + "action_time": "2020-06-14T09:50:23.835", + "user": 1, + "content_type": 1, + "object_id": "3", + "object_repr": "340 Wintergreen Avenue", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 13, + "fields": { + "action_time": "2020-06-14T09:50:52.590", + "user": 1, + "content_type": 1, + "object_id": "4", + "object_repr": "9230 E. Joy Ridge Street", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 14, + "fields": { + "action_time": "2020-06-14T09:51:21.192", + "user": 1, + "content_type": 1, + "object_id": "5", + "object_repr": "9606 Harvard Street", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 15, + "fields": { + "action_time": "2020-06-14T09:51:46.901", + "user": 1, + "content_type": 1, + "object_id": "6", + "object_repr": "588 Argyle Avenue", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 16, + "fields": { + "action_time": "2020-06-14T09:52:18.053", + "user": 1, + "content_type": 3, + "object_id": "1", + "object_repr": "Joshua Tree Green Haus /w Hot Tub", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 17, + "fields": { + "action_time": "2020-06-14T09:52:32.121", + "user": 1, + "content_type": 3, + "object_id": "2", + "object_repr": "Oceanview Retreat", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 18, + "fields": { + "action_time": "2020-06-14T09:52:43.896", + "user": 1, + "content_type": 3, + "object_id": "3", + "object_repr": "'Silo Studio' Cottage", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 19, + "fields": { + "action_time": "2020-06-14T09:52:59.316", + "user": 1, + "content_type": 3, + "object_id": "4", + "object_repr": "Pirates of the Caribbean Getaway", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 20, + "fields": { + "action_time": "2020-06-14T09:53:41.990", + "user": 1, + "content_type": 3, + "object_id": "5", + "object_repr": "The Mushroom Dome Retreat & LAND of Paradise Suite", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 21, + "fields": { + "action_time": "2020-06-14T09:53:54.720", + "user": 1, + "content_type": 3, + "object_id": "6", + "object_repr": "Underground Hygge", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 22, + "fields": { + "action_time": "2020-06-14T09:54:29.736", + "user": 1, + "content_type": 2, + "object_id": "1", + "object_repr": "HeadlinesGazer", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 23, + "fields": { + "action_time": "2020-06-14T09:54:41.934", + "user": 1, + "content_type": 2, + "object_id": "2", + "object_repr": "DavWin", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 24, + "fields": { + "action_time": "2020-06-14T09:54:53.201", + "user": 1, + "content_type": 2, + "object_id": "3", + "object_repr": "AirWow", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "admin.logentry", + "pk": 25, + "fields": { + "action_time": "2020-06-14T09:55:06.931", + "user": 1, + "content_type": 2, + "object_id": "4", + "object_repr": "4meRomance", + "action_flag": 1, + "change_message": "[{\"added\": {}}]" + } +}, +{ + "model": "profiles.profile", + "pk": 1, + "fields": { + "user": 5, + "favorite_city": "Buenos Aires" + } +}, +{ + "model": "profiles.profile", + "pk": 2, + "fields": { + "user": 4, + "favorite_city": "Barcelona" + } +}, +{ + "model": "profiles.profile", + "pk": 3, + "fields": { + "user": 3, + "favorite_city": "Budapest" + } +}, +{ + "model": "profiles.profile", + "pk": 4, + "fields": { + "user": 2, + "favorite_city": "Berlin" + } +} +] diff --git a/start.sh b/start.sh index 364ef5333d..60e341dca0 100644 --- a/start.sh +++ b/start.sh @@ -4,6 +4,9 @@ set -e echo "Applying migrations..." python manage.py migrate --noinput +echo "Loading initial data..." +python manage.py loaddata fixtures/initial_data.json + echo "Starting Gunicorn..." exec gunicorn oc_lettings_site.wsgi:application \ --bind 0.0.0.0:${PORT:-8000} \ No newline at end of file From a19a4cfab7d11b8bd8104365b1b2b15f5bd48e18 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 21 Jun 2026 13:14:31 +0200 Subject: [PATCH 084/113] Fixed a minor file name error in start.sh --- start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.sh b/start.sh index 60e341dca0..1369cace98 100644 --- a/start.sh +++ b/start.sh @@ -5,7 +5,7 @@ echo "Applying migrations..." python manage.py migrate --noinput echo "Loading initial data..." -python manage.py loaddata fixtures/initial_data.json +python manage.py loaddata data.json echo "Starting Gunicorn..." exec gunicorn oc_lettings_site.wsgi:application \ From 3271794607532406302081990ddaf87323a4dfab Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 21 Jun 2026 13:36:44 +0200 Subject: [PATCH 085/113] Added a third job to pipeline to automate deployment after a successful containerization --- .github/workflows/pipeline.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index e115f85416..3667be71ea 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -71,3 +71,16 @@ jobs: - name: Push Docker image (latest) run: | docker push ${{ secrets.DOCKER_USERNAME }}/oc_lettings_site:latest + + deploy: + + needs: containerize + + if: github.ref == 'refs/heads/master' + + runs-on: ubuntu-latest + + #Trigger Render deployment + steps: + - name: Trigger Render Deploy Hook + run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK }} \ No newline at end of file From d5fd4b6ed0319174288b91da719abfe343729020 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 21 Jun 2026 13:41:09 +0200 Subject: [PATCH 086/113] Testing full pipeline --- global_templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global_templates/index.html b/global_templates/index.html index 40e5dd5911..34cb562c7c 100644 --- a/global_templates/index.html +++ b/global_templates/index.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Holiday Homes{% endblock title %} +{% block title %}Holiday Homes V2{% endblock title %} {% block content %} From ccc30394f3a4aa5ae2cc9d68c571991aaf2c19bc Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 21 Jun 2026 14:14:00 +0200 Subject: [PATCH 087/113] Removing the loading data from the start.sh script All technical requirements are now fulfilled --- start.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/start.sh b/start.sh index 1369cace98..364ef5333d 100644 --- a/start.sh +++ b/start.sh @@ -4,9 +4,6 @@ set -e echo "Applying migrations..." python manage.py migrate --noinput -echo "Loading initial data..." -python manage.py loaddata data.json - echo "Starting Gunicorn..." exec gunicorn oc_lettings_site.wsgi:application \ --bind 0.0.0.0:${PORT:-8000} \ No newline at end of file From e551a1576b3402b5304e002ea874b3fef7d92534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Muller?= <156411340+Jeremuller@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:43:20 +0200 Subject: [PATCH 088/113] Update README.md --- README.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c8547803f7..04b37f80da 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,97 @@ -## Résumé +# Orange County Lettings + +## Présentation du projet + +Orange County Lettings est une application web développée avec Django pour une start-up spécialisée dans la location de biens immobiliers aux États-Unis. + +Dans un contexte de croissance de son activité, l'entreprise souhaite faire évoluer son application afin d'améliorer sa maintenabilité, sa fiabilité et son processus de déploiement. L'application était initialement organisée sous la forme d'un monolithe. + +Dans le cadre de son évolution, elle a été restructurée afin de séparer les fonctionnalités métier en applications Django indépendantes (lettings et profiles). Le module oc_lettings_site conserve désormais les responsabilités de configuration globale du projet (paramétrage, routage principal, serveur WSGI et ressources communes). + +Ce projet met l'accent sur l'industrialisation d'une application Django. L'infrastructure a été modernisée afin d'intégrer une chaîne complète d'intégration et de déploiement continus (CI/CD), comprenant la conteneurisation avec Docker, l'automatisation des tests et des déploiements avec GitHub Actions, la publication des images sur DockerHub et un déploiement automatisé sur Render avec une base de données PostgreSQL en production. + +Cette documentation présente l'architecture de l'application, les technologies employées, les procédures d'installation, ainsi que les étapes de déploiement et de maintenance du projet. + +## Fonctionnalités + +L’application vise à fournir une interface simple de consultation de contenus immobiliers, structurée autour de deux modules principaux : les annonces et les profils utilisateurs. + +### Consultation des annonces immobilières +- Affichage de la liste des annonces disponibles (lettings) +- Consultation du détail d’une annonce (adresse complète et informations associées) + +### Gestion des profils utilisateurs +- Affichage de la liste des profils utilisateurs +- Consultation du détail d’un profil (nom d’utilisateur et informations associées) + +### Page d’accueil +- Page d’entrée de l’application présentant une navigation vers les principales sections + +### Interface d’administration +- Accès à l’interface d’administration Django +- Gestion des données (profils et annonces) via l’admin intégré + +## Architecture globale + +L’application Orange County Lettings repose sur une architecture Django modulaire organisée autour de plusieurs composants indépendants. + +### Architecture applicative Django Le projet est structuré en trois éléments principaux : +- oc_lettings_site : projet Django principal contenant la configuration globale (settings, urls, wsgi) ainsi que les ressources communes. +- lettings : application métier dédiée à la gestion des annonces immobilières. +- profiles : application métier dédiée à la gestion des profils utilisateurs. + +Cette séparation permet une meilleure modularité et une évolution indépendante des fonctionnalités. + +### Architecture de déploiement L’application est conçue pour fonctionner dans un environnement conteneurisé et automatisé : +- L’application est exécutée dans un conteneur Docker. +- L’image Docker est construite et versionnée via une pipeline CI/CD. +- Les images sont stockées sur DockerHub. +- Le déploiement est automatisé sur la plateforme Render. + +### Base de données +L’application utilise une stratégie de base de données différenciée selon les environnements afin de garantir cohérence et reproductibilité: +- Environnement de développement : PostgreSQL est utilisé via Docker Compose afin de reproduire un environnement proche de la production. +- Environnement de production : PostgreSQL managé est utilisé sur la plateforme Render. +- Démarrage rapide et intégration continue : SQLite est utilisé pour les scénarios de quick start ainsi que pour l'exécution des tests unitaires dans la pipeline CI, offrant une configuration légère et rapide à mettre en œuvre. + +La configuration de la base de données est gérée via des variables d’environnement, permettant d’adapter automatiquement le comportement de l’application selon le contexte d’exécution. + +### Vue d’ensemble du flux applicatif +Code source → GitHub Actions → Docker Image → DockerHub → Render → Application déployée avec PostgreSQL + + +## Stack technique + +### Langage et framework principal +- Python 3.10 : langage principal du projet +- Django 3.0 : framework web utilisé pour structurer l’application selon une architecture MVC + +### Serveur d’application +- Gunicorn : serveur WSGI utilisé pour l’exécution de l’application en environnement de production + +### Base de données +SQLite : utilisé pour les tests et scénarios de démarrage rapide +PostgreSQL : utilisé en développement (Docker Compose) et en production (Render) +Conteneurisation et déploiement +Docker : containerisation de l’application +Docker Compose : orchestration des services en environnement de développement +DockerHub : registre d’images Docker +Render : plateforme d’hébergement et de déploiement automatisé +CI/CD +GitHub Actions : pipeline d’intégration et de déploiement continus +Automatisation des étapes de test, linting, build et déploiement +Qualité de code et tests +pytest / pytest-django : tests unitaires de l’application Django +coverage : mesure de la couverture de tests +flake8 : analyse statique du code (PEP8) +black : formattage automatique du code +Sécurité et production +sentry-sdk : monitoring des erreurs en production +whitenoise : gestion des fichiers statiques en production +Variables d’environnement +python-dotenv : gestion des variables d’environnement en développement + -Site web d'Orange County Lettings ## Développement local From 027cb31425c24fa3aabbb6afb5872402d4fe1af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Muller?= <156411340+Jeremuller@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:30:53 +0200 Subject: [PATCH 089/113] Update README.md --- README.md | 122 +++++++++++++++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 04b37f80da..f73fedef11 100644 --- a/README.md +++ b/README.md @@ -49,89 +49,93 @@ Cette séparation permet une meilleure modularité et une évolution indépendan - Le déploiement est automatisé sur la plateforme Render. ### Base de données -L’application utilise une stratégie de base de données différenciée selon les environnements afin de garantir cohérence et reproductibilité: -- Environnement de développement : PostgreSQL est utilisé via Docker Compose afin de reproduire un environnement proche de la production. -- Environnement de production : PostgreSQL managé est utilisé sur la plateforme Render. -- Démarrage rapide et intégration continue : SQLite est utilisé pour les scénarios de quick start ainsi que pour l'exécution des tests unitaires dans la pipeline CI, offrant une configuration légère et rapide à mettre en œuvre. +L'application s'appuie sur une stratégie de gestion des données adaptée aux différents environnements d'exécution (développement, intégration continue et production). Cette approche permet de concilier simplicité d'utilisation, reproductibilité des environnements et cohérence avec la production. La configuration de la base de données est gérée via des variables d’environnement, permettant d’adapter automatiquement le comportement de l’application selon le contexte d’exécution. +Les technologies employées et leur répartition sont détaillées dans la section Stack technique. + ### Vue d’ensemble du flux applicatif Code source → GitHub Actions → Docker Image → DockerHub → Render → Application déployée avec PostgreSQL +## Stack technique -## Stack technique +### Langage et framework principal +- Python 3.10 : langage principal du projet +- Django 3.0 : framework web utilisé pour structurer l’application selon une architecture MVC -### Langage et framework principal -- Python 3.10 : langage principal du projet -- Django 3.0 : framework web utilisé pour structurer l’application selon une architecture MVC +### Base de données +- SQLite : utilisé pour les tests et scénarios de démarrage rapide +- PostgreSQL : utilisé en développement (Docker Compose) et en production (Render) -### Serveur d’application -- Gunicorn : serveur WSGI utilisé pour l’exécution de l’application en environnement de production +### Conteneurisation +- Docker : containerisation de l’application +- Docker Compose : orchestration des services en environnement de développement +- DockerHub : registre d’images Docker -### Base de données -SQLite : utilisé pour les tests et scénarios de démarrage rapide -PostgreSQL : utilisé en développement (Docker Compose) et en production (Render) -Conteneurisation et déploiement -Docker : containerisation de l’application -Docker Compose : orchestration des services en environnement de développement -DockerHub : registre d’images Docker -Render : plateforme d’hébergement et de déploiement automatisé -CI/CD -GitHub Actions : pipeline d’intégration et de déploiement continus -Automatisation des étapes de test, linting, build et déploiement -Qualité de code et tests -pytest / pytest-django : tests unitaires de l’application Django -coverage : mesure de la couverture de tests -flake8 : analyse statique du code (PEP8) -black : formattage automatique du code -Sécurité et production -sentry-sdk : monitoring des erreurs en production -whitenoise : gestion des fichiers statiques en production -Variables d’environnement -python-dotenv : gestion des variables d’environnement en développement +### CI/CD +- GitHub Actions : pipeline d’intégration et de déploiement continus +- Automatisation des étapes de test, linting, build et déploiement +### Qualité de code et tests +- pytest / pytest-django : tests unitaires de l’application Django +- coverage : mesure de la couverture de tests +- flake8 : analyse statique du code (PEP8) +- black : formattage automatique du code +### Monitoring +- sentry-sdk : monitoring des erreurs en production -## Développement local +### Production +- Render : plateforme d’hébergement et de déploiement automatisé +- Gunicorn : serveur WSGI utilisé pour l’exécution de l’application en environnement de production +- whitenoise : gestion des fichiers statiques en production -### Prérequis -- Compte GitHub avec accès en lecture à ce repository -- Git CLI -- SQLite3 CLI -- Interpréteur Python, version 3.6 ou supérieure +### Variables d’environnement +- python-dotenv : gestion des variables d’environnement en développement -Dans le reste de la documentation sur le développement local, il est supposé que la commande `python` de votre OS shell exécute l'interpréteur Python ci-dessus (à moins qu'un environnement virtuel ne soit activé). -### macOS / Linux +## Usage local -#### Cloner le repository +### Prérequis -- `cd /path/to/put/project/in` -- `git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git` +Avant de lancer l'application, assurez-vous que les outils suivants sont installés sur votre machine : +- Compte GitHub avec accès en lecture à ce repository +- Git CLI pour cloner le dépôt et gérer le code source. +- Interpréteur Python (version 3.10 recomandée pour des raisons de compatibilité) uniquement si vous souhaitez exécuter l'application hors conteneur ou contribuer au développement. +- Docker (incluant Docker Compose) : pour exécuter l'application dans un environnement conteneurisé. -#### Créer l'environnement virtuel +Dans le reste de la documentation sur le développement local, il est supposé que la commande python de votre OS shell exécute l'interpréteur Python ci-dessus (à moins qu'un environnement virtuel ne soit activé). -- `cd /path/to/Python-OC-Lettings-FR` -- `python -m venv venv` -- `apt-get install python3-venv` (Si l'étape précédente comporte des erreurs avec un paquet non trouvé sur Ubuntu) -- Activer l'environnement `source venv/bin/activate` -- Confirmer que la commande `python` exécute l'interpréteur Python dans l'environnement virtuel -`which python` -- Confirmer que la version de l'interpréteur Python est la version 3.6 ou supérieure `python --version` -- Confirmer que la commande `pip` exécute l'exécutable pip dans l'environnement virtuel, `which pip` -- Pour désactiver l'environnement, `deactivate` +### Cloner le repository -#### Exécuter le site +- cd /path/to/put/project/in +- git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git -- `cd /path/to/Python-OC-Lettings-FR` -- `source venv/bin/activate` -- `pip install --requirement requirements.txt` -- `python manage.py runserver` -- Aller sur `http://localhost:8000` dans un navigateur. +### Créer l'environnement virtuel + +Pour le développement local, il est recommandé d'utiliser un environnement virtuel Python : +- cd /path/to/Python-OC-Lettings-FR +- python -m venv venv +- apt-get install python3-venv (Si l'étape précédente comporte des erreurs avec un paquet non trouvé sur Ubuntu) +- Activer l'environnement: Linux / macOS: source venv/bin/activate ou .\venv\Scripts\Activate.ps pour Windows (PowerShell) +- Confirmer que la commande python exécute l'interpréteur Python dans l'environnement virtuel which python +- Confirmer que la version de l'interpréteur Python est la version 3.6 ou supérieure python --version +- Confirmer que la commande pip exécute l'exécutable pip dans l'environnement virtuel, which pip +- Pour désactiver l'environnement, deactivate + +Les sections suivantes présentent les différentes méthodes d'exécution de l'application, selon que vous souhaitiez simplement la découvrir ou contribuer à son développement. + +### Exécuter le site (quick start) +- cd /path/to/Python-OC-Lettings-FR +- source venv/bin/activate +- pip install --requirement requirements.txt +- python manage.py runserver +- Aller sur http://localhost:8000 dans un navigateur. - Confirmer que le site fonctionne et qu'il est possible de naviguer (vous devriez voir plusieurs profils et locations). + #### Linting - `cd /path/to/Python-OC-Lettings-FR` @@ -160,8 +164,6 @@ Dans le reste de la documentation sur le développement local, il est supposé q - Aller sur `http://localhost:8000/admin` - Connectez-vous avec l'utilisateur `admin`, mot de passe `Abc1234!` -### Windows - Utilisation de PowerShell, comme ci-dessus sauf : - Pour activer l'environnement virtuel, `.\venv\Scripts\Activate.ps1` From 7421908325de6d0207c1f5229539245264fe2cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Muller?= <156411340+Jeremuller@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:54:53 +0200 Subject: [PATCH 090/113] Update README.md --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f73fedef11..4f1789727d 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,6 @@ Code source → GitHub Actions → Docker Image → DockerHub → Render → App ### Variables d’environnement - python-dotenv : gestion des variables d’environnement en développement - ## Usage local ### Prérequis @@ -135,6 +134,51 @@ Les sections suivantes présentent les différentes méthodes d'exécution de l' - Aller sur http://localhost:8000 dans un navigateur. - Confirmer que le site fonctionne et qu'il est possible de naviguer (vous devriez voir plusieurs profils et locations). +### Développement avec Docker Compose + +Pour reproduire un environnement proche de la production, le projet fournit une configuration Docker Compose permettant d'exécuter simultanément l'application Django et une base de données PostgreSQL. + +### Configuration + +Avant le premier lancement, créez un fichier `.env` à la racine du projet. + +Ce fichier contient les variables d'environnement nécessaires à la configuration de l'application et de la base de données. Pour des raisons de sécurité, il n'est pas versionné dans le dépôt Git. + +Les variables suivantes sont notamment requises : + +* `DEBUG`=False +* `USE_SQLITE`=False +* `POSTGRES_DB`=oc_lettings +* `POSTGRES_USER`=oc_user +* `POSTGRES_HOST`=db +* `POSTGRES_PORT`=5432 + +Les variables suivantes contiennent des informations sensibles. Leurs valeurs ne sont pas versionnées dans le dépôt Git et doivent être fournies séparément. + +* `SECRET_KEY` +* `POSTGRES_PASSWORD` +* `SENTRY_DSN` + +### Démarrage de l'environnement + +Une fois le fichier `.env` créé, démarrez les services à l'aide de Docker Compose : + + +docker compose up --build + +Au démarrage, le conteneur de l'application applique automatiquement les migrations de la base de données avant de lancer le serveur Gunicorn. + +L'application est ensuite accessible à l'adresse : + +http://localhost:8000 + +Les services sont exécutés au premier plan. Pour arrêter l'environnement, utilisez Ctrl + C ou lancez Docker Compose en mode détaché avec docker compose up -d. + +### Données de démonstration + +L'environnement de développement démarre avec une base PostgreSQL vide. + +Si vous souhaitez disposer d'un jeu de données d'exemple, les fixtures fournies avec le projet peuvent être chargées à l'aide de la commande `loaddata`. Cette procédure est décrite dans la documentation technique. #### Linting From 9d956b772962ce26ab18b400f4ed842f98d1a4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Muller?= <156411340+Jeremuller@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:59:27 +0200 Subject: [PATCH 091/113] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4f1789727d..5dbf63d5e4 100644 --- a/README.md +++ b/README.md @@ -110,27 +110,27 @@ Dans le reste de la documentation sur le développement local, il est supposé q ### Cloner le repository - cd /path/to/put/project/in -- git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git +- `git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git` ### Créer l'environnement virtuel Pour le développement local, il est recommandé d'utiliser un environnement virtuel Python : - cd /path/to/Python-OC-Lettings-FR -- python -m venv venv -- apt-get install python3-venv (Si l'étape précédente comporte des erreurs avec un paquet non trouvé sur Ubuntu) -- Activer l'environnement: Linux / macOS: source venv/bin/activate ou .\venv\Scripts\Activate.ps pour Windows (PowerShell) -- Confirmer que la commande python exécute l'interpréteur Python dans l'environnement virtuel which python -- Confirmer que la version de l'interpréteur Python est la version 3.6 ou supérieure python --version -- Confirmer que la commande pip exécute l'exécutable pip dans l'environnement virtuel, which pip -- Pour désactiver l'environnement, deactivate +- `python -m venv venv` +- `apt-get install python3-venv` (Si l'étape précédente comporte des erreurs avec un paquet non trouvé sur Ubuntu) +- Activer l'environnement: Linux / macOS: `source venv/bin/activate` ou `.\venv\Scripts\Activate.ps` pour Windows (PowerShell) +- Confirmer que la commande python exécute l'interpréteur Python dans l'environnement virtuel `which python ` +- Confirmer que la version de l'interpréteur Python est la version 3.6 ou supérieure `python --version ` +- Confirmer que la commande pip exécute l'exécutable pip dans l'environnement virtuel, `which pip ` +- Pour désactiver l'environnement, `deactivate ` Les sections suivantes présentent les différentes méthodes d'exécution de l'application, selon que vous souhaitiez simplement la découvrir ou contribuer à son développement. ### Exécuter le site (quick start) - cd /path/to/Python-OC-Lettings-FR -- source venv/bin/activate -- pip install --requirement requirements.txt -- python manage.py runserver +- `source venv/bin/activate ` +- `pip install --requirement requirements.txt ` +- `python manage.py runserver ` - Aller sur http://localhost:8000 dans un navigateur. - Confirmer que le site fonctionne et qu'il est possible de naviguer (vous devriez voir plusieurs profils et locations). From 6f1d28a92817cd46eb24e06bca4bef1a8d131f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Muller?= <156411340+Jeremuller@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:00:14 +0200 Subject: [PATCH 092/113] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5dbf63d5e4..363fd94b9b 100644 --- a/README.md +++ b/README.md @@ -109,13 +109,13 @@ Dans le reste de la documentation sur le développement local, il est supposé q ### Cloner le repository -- cd /path/to/put/project/in +- `cd /path/to/put/project/in ` - `git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git` ### Créer l'environnement virtuel Pour le développement local, il est recommandé d'utiliser un environnement virtuel Python : -- cd /path/to/Python-OC-Lettings-FR +- `cd /path/to/Python-OC-Lettings-FR ` - `python -m venv venv` - `apt-get install python3-venv` (Si l'étape précédente comporte des erreurs avec un paquet non trouvé sur Ubuntu) - Activer l'environnement: Linux / macOS: `source venv/bin/activate` ou `.\venv\Scripts\Activate.ps` pour Windows (PowerShell) @@ -127,7 +127,7 @@ Pour le développement local, il est recommandé d'utiliser un environnement vir Les sections suivantes présentent les différentes méthodes d'exécution de l'application, selon que vous souhaitiez simplement la découvrir ou contribuer à son développement. ### Exécuter le site (quick start) -- cd /path/to/Python-OC-Lettings-FR +- `cd /path/to/Python-OC-Lettings-FR ` - `source venv/bin/activate ` - `pip install --requirement requirements.txt ` - `python manage.py runserver ` From 19e5c3cb759554cb445559b71d1da08050a4d8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Muller?= <156411340+Jeremuller@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:02:44 +0200 Subject: [PATCH 093/113] Update README.md --- README.md | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 208 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 363fd94b9b..15d00740f7 100644 --- a/README.md +++ b/README.md @@ -180,19 +180,201 @@ L'environnement de développement démarre avec une base PostgreSQL vide. Si vous souhaitez disposer d'un jeu de données d'exemple, les fixtures fournies avec le projet peuvent être chargées à l'aide de la commande `loaddata`. Cette procédure est décrite dans la documentation technique. -#### Linting +## Tests et qualité du code + +Le projet intègre plusieurs outils destinés à garantir la qualité du code et le bon fonctionnement de l'application. Les commandes ci-dessous sont à exécuter depuis la racine du projet, après activation de l'environnement virtuel : - `cd /path/to/Python-OC-Lettings-FR` - `source venv/bin/activate` + +### Linting + +La conformité du code aux règles de style (PEP 8) peut être vérifiée à l'aide de la commande suivante : + - `flake8` -#### Tests unitaires +### Tests unitaires + +Les tests unitaires sont exécutés avec `pytest` : -- `cd /path/to/Python-OC-Lettings-FR` -- `source venv/bin/activate` - `pytest` -#### Base de données +### Couverture de tests + +La couverture des tests peut être mesurée avec : + +- `pytest --cov` + +Ces vérifications sont exécutées automatiquement par la pipeline d'intégration continue à chaque `push` et à chaque ouverture ou mise à jour d'une Pull Request. Les différentes étapes de cette pipeline sont détaillées dans la section suivante. + +## Pipeline CI/CD + +Le projet intègre une pipeline d'intégration et de déploiement continus (CI/CD) mise en œuvre avec GitHub Actions. + +À chaque `push` ou ouverture de Pull Request, la pipeline exécute automatiquement les étapes nécessaires pour vérifier la qualité du code. Lorsqu'une modification est intégrée à la branche `master`, elle poursuit le processus jusqu'à la construction de l'image Docker et au déploiement automatique de l'application sur Render. + +### Vue d'ensemble + +```text +Push / Pull Request + │ + ▼ +GitHub Actions + │ + ▼ +Job "compile" + │ + ├── Installation des dépendances + ├── Analyse statique (flake8) + └── Tests unitaires + couverture + │ + └──────────────► Validation réussie + │ + ▼ + (push sur master uniquement) + │ + ▼ + Job "containerize" + │ + ├── Build Docker + ├── Tag SHA + ├── Tag latest + └── Push DockerHub + │ + ▼ + Job "deploy" + │ + ▼ + Render +``` + +### Job `compile` + +Ce premier job est exécuté à chaque `push` et à chaque Pull Request. + +Il réalise les opérations suivantes : + +* récupération du dépôt Git ; +* installation de Python 3.10 ; +* installation des dépendances du projet ; +* exécution de l'analyse statique avec `flake8` ; +* exécution des tests unitaires avec `pytest` ; +* vérification d'un seuil minimal de 80 % de couverture de tests. + +Le job compile s'exécute dans un environnement GitHub Actions éphémère. À chaque exécution, une nouvelle machine virtuelle est provisionnée, les dépendances du projet sont installées, puis les opérations de linting et de tests sont exécutées. Une fois le job terminé, cet environnement est automatiquement détruit, garantissant des exécutions indépendantes et reproductibles sans impact sur les environnements de développement ou de production. + +Pour cette étape, l'application est configurée pour utiliser SQLite afin de disposer d'un environnement de test léger et reproductible. + +### Job `containerize` + +Ce job est exécuté uniquement lors d'un `push` sur la branche `master`. + +Après authentification auprès de DockerHub, il : + +* construit l'image Docker de l'application ; +* applique deux tags (`latest` et le SHA du commit) ; +* publie les deux images sur DockerHub. + +Cette stratégie permet de disposer à la fois d'une image représentant la dernière version stable et d'une image correspondant exactement à un commit donné. + +### Job `deploy` + +Une fois l'image publiée, le dernier job déclenche automatiquement le **Deploy Hook** de Render. + +Render télécharge alors la dernière image disponible sur DockerHub et redéploie l'application sans intervention manuelle. + +### Gestion des secrets + +Les informations sensibles utilisées par la pipeline sont stockées dans les **GitHub Actions Secrets**. + +Cette approche permet de ne jamais versionner les secrets dans le dépôt Git tout en les rendant accessibles aux différentes étapes de la pipeline. + +Les secrets utilisés comprennent notamment : + +* `SECRET_KEY` +* `SENTRY_DSN` +* `DOCKER_USERNAME` +* `DOCKER_PASSWORD` +* `RENDER_DEPLOY_HOOK` + + +Cette architecture permet de garantir qu'aucun déploiement en production n'est réalisé tant que les étapes de validation (linting, tests unitaires et couverture de code) n'ont pas été exécutées avec succès. + +## Déploiement sur Render + +L’application est déployée automatiquement sur la plateforme **Render**, qui héberge le service web ainsi que la base de données PostgreSQL en environnement de production. + +Le déploiement est entièrement automatisé et s’appuie sur la pipeline CI/CD ainsi que sur une architecture conteneurisée via Docker. + +### Architecture de déploiement + +Le déploiement repose sur le flux suivant : + +- GitHub Actions construit et publie une image Docker sur DockerHub +- Render récupère automatiquement cette image via un **Deploy Hook** +- L’application est redéployée sans intervention manuelle + +### Service web Render + +Le service web Render est configuré pour : + +- exécuter un conteneur Docker basé sur l’image publiée sur DockerHub +- exposer l’application via un port dynamique fourni par Render +- exécuter automatiquement le script `start.sh` au démarrage + +### Script de démarrage (`start.sh`) + +Au lancement du conteneur, le script `start.sh` est exécuté automatiquement. + +Il effectue les opérations suivantes : + +- application des migrations Django sur la base de données PostgreSQL +- lancement du serveur WSGI via Gunicorn + +Ce mécanisme garantit que la base de données est toujours synchronisée avec le schéma de l’application au moment du déploiement. + +### Base de données PostgreSQL + +En production, l’application utilise une base de données PostgreSQL managée par Render. Les informations de connexion sont fournies via des variables d’environnement injectées directement dans le service Render. Ces variables ne sont jamais versionnées dans le dépôt Git. + +### Variables d’environnement + +Render gère la configuration de l’application via des variables d’environnement définies dans son interface. + +Elles permettent notamment de : + +- Configurer la connexion à la base de données PostgreSQL +- Activer ou désactiver le mode debug +- Fournir les clés de services externes (Sentry, etc.) + +### Healthcheck + +Render effectue un **healthcheck HTTP** sur l’application afin de vérifier que le service répond correctement après déploiement. + +Ce mécanisme permet de s’assurer que : + +- le conteneur démarre correctement +- le serveur Gunicorn est opérationnel +- l’application est accessible avant d’exposer le service + +En cas d’échec du healthcheck, le déploiement est considéré comme non valide. + +### Déclenchement du déploiement + +Le déploiement est automatiquement déclenché lorsqu’un commit (ou une pull request) est validé(e) sur la branche principale (master). + +Le job deploy de la pipeline CI/CD envoie une requête HTTP au Deploy Hook Render, ce qui provoque : + +- La récupération de la dernière image Docker +- Le redémarrage du service +- Exécution automatique du healthcheck +- La mise en production immédiate des modifications si le healthcheck est un succès + +Une fois le déploiement terminé, l’application est accessible publiquement via l’URL fournie par Render. + +## Autres commandes utiles + +### Base de données - `cd /path/to/Python-OC-Lettings-FR` - Ouvrir une session shell `sqlite3` @@ -203,12 +385,29 @@ Si vous souhaitez disposer d'un jeu de données d'exemple, les fixtures fournies Python-OC-Lettings-FR_profile where favorite_city like 'B%';` - `.quit` pour quitter -#### Panel d'administration +### Panel d'administration - Aller sur `http://localhost:8000/admin` - Connectez-vous avec l'utilisateur `admin`, mot de passe `Abc1234!` -Utilisation de PowerShell, comme ci-dessus sauf : +## Liens utiles + +Les documentations officielles suivantes permettent d'approfondir les technologies utilisées dans ce projet : + +- GitHub : https://docs.github.com/fr +- Docker : https://docs.docker.com/ +- GitHub Actions : https://docs.github.com/fr/actions +- Render : https://render.com/docs + +## Documentation complète + +La documentation technique du projet est générée avec **Sphinx** et publiée via **Read the Docs**. + +Elle est accessible ici : +👉 + +## Auteur + +Jérémy Muller, étudiant en développement applicatif python chez OpenClassrooms. -- Pour activer l'environnement virtuel, `.\venv\Scripts\Activate.ps1` -- Remplacer `which ` par `(Get-Command ).Path` +GitHub : https://github.com/Jeremuller/ From a7dd1dc366b6005e4165813809eb0d87a8b4813a Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Wed, 1 Jul 2026 13:11:58 +0200 Subject: [PATCH 094/113] Updated docker-compose.yml during documentation writing to use start.sh as the dockerfile and the production does --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 050b3f31c7..81b9bc9c24 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: - "8000:8000" depends_on: - db - command: gunicorn oc_lettings_site.wsgi:application --bind 0.0.0.0:8000 + command: ./start.sh volumes: oc_lettings_postgres_data: \ No newline at end of file From 7502fe0a8905e6c5e8fd5b93b888ff3f447ca800 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 3 Jul 2026 08:46:05 +0200 Subject: [PATCH 095/113] Added documentation related libraries in requirements.txt --- requirements.txt | Bin 1296 -> 2434 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 24772fb839cd694f2ff37d38fdbc4c4c34221b55..8a0119c4375d1a642fe670e05d69857e45d18f4b 100644 GIT binary patch literal 2434 zcmbW2Sx*~55QOJBQhtgfcDWz&mZwOOBJl%|4{R^^SbSj2k55ux*N(?QBq)((WTvm` z>YARPKl*8w`e~r=I&IQjTBXnWROwSXiSNhsLAFKOrzS1KcBbHv#{1-y*gwc1cG6D=&a{q$Lk~!Jq%PERT%3Fx>}YxUC0BkI-PfZQ`K3i zk2`hJ6XvD12P|B$y*W*BOMUHDt;HbTB#i6h6P|d=YHdM>X=S zzNzv`Dx>ck{T_F991jOznXAT7R`ixK*h6Rg-D#?e+KtMl1^BxG!dz4DjY9B1AO9eV`b7@2QouKiuH6LD+37LO|Gm1-!R zN}vacZ$rkCt-a{N_kr71w8w0U*XWI#=d2~;G-lV6GJn5k%)mVq-(~cdCr#C&Jy(7S zU6q+cZO>t`(vB@`yj^VDnWo`= zJ`dKt)j{hhdb*JgrlA1mO7@OJFw0dmMQyZEfUz ztG`oqhysYrNUY4`2;XpaTBB-IZuLQT#Lx6Qvr?x1w|KPs^q;X)p`NSe>&me7KJxo+ z=J|G+r`HULVNI?UrKMP-2Gd8KgJ~TYl{cdK=FQy z(H>Ujw$aB^x4LpK=m~c7o9=hjys~RrS=g9=-nC}5?9y#iB;!B|%edK7*7R0N@Eelf zE>NF!n+AS&!TeC%^TvSQw-w~vM5Ej3d>{+2Hydwr@G3iTryNam&aG_ZVGi(Mzvf)O E06n{HWdHyG delta 46 zcmV+}0MY+~6Oal5|NfCgB$0+llLP^NlcoW#ld1wFvl0VT0+WgbM3XoOAd`Fua+6vK ED41^$1^@s6 From 932c3c5228bb861644fc4c939cdbd975bf2572f7 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 3 Jul 2026 09:51:54 +0200 Subject: [PATCH 096/113] Initialize Sphinx documentation --- .gitignore | 3 ++- docs/Makefile | 20 ++++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++++++++++++ docs/source/conf.py | 27 +++++++++++++++++++++++++++ docs/source/index.rst | 17 +++++++++++++++++ 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst diff --git a/.gitignore b/.gitignore index 04ea67a31a..85542bd19f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ venv .idea/ .env *.log -db.sqlite3 \ No newline at end of file +db.sqlite3 +docs/build/ \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000..d0c3cbf102 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000000..dc1312ab09 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000000..7f1c3d0f81 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,27 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'Orange County Lettings' +copyright = '2026, Jeremy Muller' +author = 'Jeremy Muller' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [] + +templates_path = ['_templates'] +exclude_patterns = [] + +language = 'fr' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'alabaster' +html_static_path = ['_static'] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000000..d5053c7ee9 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,17 @@ +.. Orange County Lettings documentation master file, created by + sphinx-quickstart on Thu Jul 2 19:24:47 2026. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Orange County Lettings documentation +==================================== + +Add your content using ``reStructuredText`` syntax. See the +`reStructuredText `_ +documentation for details. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + From e06a0c868a5abef2b12712652a79e639c836190f Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 3 Jul 2026 09:53:19 +0200 Subject: [PATCH 097/113] Added documentation related libraries in requirements.txt --- requirements.txt | Bin 2434 -> 2430 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8a0119c4375d1a642fe670e05d69857e45d18f4b..8cb425ba2d128746a0943f72d9ce3f60b3ef4791 100644 GIT binary patch delta 7 OcmZn?{wK7djuQY3S_0hw delta 12 Tcmew-)Fiy2j+2F#fr|kE8~p Date: Fri, 3 Jul 2026 09:53:33 +0200 Subject: [PATCH 098/113] Added documentation related libraries in requirements.txt --- requirements.txt | Bin 2430 -> 2434 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8cb425ba2d128746a0943f72d9ce3f60b3ef4791..8a0119c4375d1a642fe670e05d69857e45d18f4b 100644 GIT binary patch delta 12 Tcmew-)Fiy2j+2F#fr|kE8~p Date: Sat, 4 Jul 2026 14:56:26 +0200 Subject: [PATCH 099/113] Started writing sphinx documentation --- docs/source/conf.py | 14 ++- docs/source/index.rst | 17 +++- docs/source/installation.rst | 161 +++++++++++++++++++++++++++++++++++ docs/source/introduction.rst | 49 +++++++++++ docs/source/local_usage.rst | 62 ++++++++++++++ 5 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 docs/source/installation.rst create mode 100644 docs/source/introduction.rst create mode 100644 docs/source/local_usage.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 7f1c3d0f81..2700780bb7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,6 +3,11 @@ # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html +import os +import sys + +sys.path.insert(0, os.path.abspath("../..")) + # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information @@ -13,15 +18,18 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", +] templates_path = ['_templates'] exclude_patterns = [] -language = 'fr' +language = 'en' # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] diff --git a/docs/source/index.rst b/docs/source/index.rst index d5053c7ee9..ea3b961c19 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,12 +6,23 @@ Orange County Lettings documentation ==================================== -Add your content using ``reStructuredText`` syntax. See the -`reStructuredText `_ -documentation for details. +Welcome to the technical documentation of the Orange County Lettings project. +This documentation explains the project's architecture, installation process, +development workflow, deployment pipeline and application components. + +Use the navigation menu below to browse each section. .. toctree:: :maxdepth: 2 :caption: Contents: + introduction + installation + local_usage + technologies + architecture + database + views_and_endpoints + usage + deployment diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 0000000000..b516d519d9 --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,161 @@ +Installation +============ + +This section explains how to install and configure the Orange County Lettings project in a local environment. + +Unlike the README, which focuses on quick usage, this section provides a more detailed explanation of the setup process and execution environments. + +The project supports two installation modes: + +- Local Python environment (development) +- Docker-based environment (production-like) + +Prerequisites +------------- + +Before installing the project, ensure the following tools are available: + +- Git +- Python 3.10+ +- pip +- Docker and Docker Compose (optional but recommended) + + +Project cloning +--------------- + +Clone the repository: + +.. code-block:: bash + + git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git + cd Python-OC-Lettings-FR + + +Local development setup +----------------------- + +This setup is intended for development without containers. + +Virtual environment +~~~~~~~~~~~~~~~~~~~ + +A virtual environment isolates the project's Python dependencies from the global interpreter. + +Create a virtual environment: + +.. code-block:: bash + + python -m venv venv + +Activate it: + +.. code-block:: bash + + # Linux / macOS + source venv/bin/activate + + # Windows + venv\Scripts\activate + +Dependency installation +~~~~~~~~~~~~~~~~~~~~~~~ + +Install dependencies: + +.. code-block:: bash + + pip install -r requirements.txt + +This installs Django together with the libraries required for testing, code quality checks and production deployment. + +Environment configuration +------------------------- + +The application relies on environment variables. + +Create a `.env` file at the root of the project. + +Required variables: + +- SECRET_KEY +- DEBUG +- USE_SQLITE +- SENTRY_DSN (optional) + +For PostgreSQL environments: + +- POSTGRES_DB +- POSTGRES_USER +- POSTGRES_PASSWORD +- POSTGRES_HOST +- POSTGRES_PORT + + +Database setup +-------------- + +Apply migrations before running the application: + +.. code-block:: bash + + python manage.py migrate + + +Run development server +---------------------- + +.. code-block:: bash + + python manage.py runserver + +Access: + +http://localhost:8000 + + +Docker setup +------------- + +The project also provides a Docker Compose configuration allowing the entire application stack to be started with a single command. + +This setup is recommended for: + +- production-like execution +- reproducibility +- avoiding local dependency issues + +Start containers: + +.. code-block:: bash + + docker compose up --build + + +Container initialization +------------------------ + +On startup, the container automatically: + +- applies database migrations +- starts the Gunicorn server + +This is handled by the `start.sh` script. + +This guarantees that the database schema is always synchronized before the application starts serving requests. + + +Environment differences +----------------------- + +- Local setup → SQLite (simpler, development-friendly) +- Docker setup → PostgreSQL (production-like) + + +Summary +------- + +Two execution modes are available: + +- Local environment → development and debugging +- Docker environment → production-like behavior \ No newline at end of file diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 0000000000..5a3430fe40 --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,49 @@ +Introduction +============ + +Project overview +---------------- + +Orange County Lettings is a web application built with the Django framework. + +The application allows users to browse property listings and view detailed information about associated profiles. + +The project follows a modular architecture in order to clearly separate concerns between the different application components (users, listings, main site, etc.). + +Development context +------------------- + +This project was developed as part of the **Python Application Developer** training program provided by OpenClassrooms. + +It serves as a practical implementation of key software engineering concepts, including: + +- web application development using Django +- modular project architecture design +- reproducible development environments +- continuous integration and deployment (CI/CD) +- production deployment automation + +Technical objectives +-------------------- + +Beyond functional implementation, this project aims to demonstrate the ability to manage a complete software development lifecycle: + +- source code management with Git and GitHub +- code quality enforcement using linting (flake8) +- unit testing with pytest +- test coverage measurement +- automated checks using GitHub Actions +- containerization with Docker +- automated deployment to production environments + +High-level architecture +----------------------- + +The application is composed of several main components: + +- a Django web application handling business logic and user interface +- a relational database system, using SQLite in development/testing and PostgreSQL in production +- a CI/CD pipeline responsible for automated testing and deployment +- a containerized deployment infrastructure using Docker and Render + +The following sections of this documentation provide details on installation, usage, system architecture, and deployment procedures. \ No newline at end of file diff --git a/docs/source/local_usage.rst b/docs/source/local_usage.rst new file mode 100644 index 0000000000..1abe5a13ba --- /dev/null +++ b/docs/source/local_usage.rst @@ -0,0 +1,62 @@ +Local Usage +=========== + +This section describes the different ways to run the application in a local environment once it has been installed. + +It includes both a simple Python-based execution and a Docker Compose execution. + +--- + +Running locally with Django +------------------------------- + +.. code-block:: bash + + python -m venv venv + source venv/bin/activate + pip install -r requirements.txt + python manage.py migrate + python manage.py runserver + +Access the application at: + +http://localhost:8000 + +Running with Docker Compose +------------------------ + +The project provides a Docker Compose configuration intended to reproduce an +environment close to production. + +Before using this execution mode, ensure that: + +- Docker is installed and running on the host machine; +- Docker Compose is available; +- a valid ``.env`` file has been created at the root of the project. + +The ``docker compose up --build`` command performs several operations: + +- builds the application image from the project's Dockerfile; +- creates a dedicated Docker network; +- starts a PostgreSQL container; +- starts the Django application container; +- automatically executes the ``start.sh`` initialization script. + +.. code-block:: bash + + docker compose up --build + +During startup, the application container automatically applies the database +migrations before launching the Gunicorn server. + +Once both containers are running, the application is available at: + +:: + + http://localhost:8000 + +--- + +Both execution modes rely on the same application codebase. + +The Django development server is generally preferred during development because it provides automatic code reloading, whereas Docker Compose offers an environment closer to production. \ No newline at end of file From 876c273bbc77be672ae31fe347270529f7bfeceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Muller?= <156411340+Jeremuller@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:54:14 +0200 Subject: [PATCH 100/113] Update README.md Big content reduction on readme to get it lighter and easier to read. --- README.md | 365 +++++++++++------------------------------------------- 1 file changed, 73 insertions(+), 292 deletions(-) diff --git a/README.md b/README.md index 15d00740f7..b62dd38aa7 100644 --- a/README.md +++ b/README.md @@ -2,218 +2,114 @@ ## Présentation du projet -Orange County Lettings est une application web développée avec Django pour une start-up spécialisée dans la location de biens immobiliers aux États-Unis. +Orange County Lettings est une application web développée avec Django pour une start-up spécialisée dans la location de biens immobiliers aux États-Unis. -Dans un contexte de croissance de son activité, l'entreprise souhaite faire évoluer son application afin d'améliorer sa maintenabilité, sa fiabilité et son processus de déploiement. L'application était initialement organisée sous la forme d'un monolithe. +Ce projet a consisté à améliorer une application existante en la restructurant autour d'une architecture Django modulaire et en mettant en place une chaîne d'intégration et de déploiement continus (CI/CD). -Dans le cadre de son évolution, elle a été restructurée afin de séparer les fonctionnalités métier en applications Django indépendantes (lettings et profiles). Le module oc_lettings_site conserve désormais les responsabilités de configuration globale du projet (paramétrage, routage principal, serveur WSGI et ressources communes). +La documentation complète du projet est disponible ici : -Ce projet met l'accent sur l'industrialisation d'une application Django. L'infrastructure a été modernisée afin d'intégrer une chaîne complète d'intégration et de déploiement continus (CI/CD), comprenant la conteneurisation avec Docker, l'automatisation des tests et des déploiements avec GitHub Actions, la publication des images sur DockerHub et un déploiement automatisé sur Render avec une base de données PostgreSQL en production. +## Fonctionnalités -Cette documentation présente l'architecture de l'application, les technologies employées, les procédures d'installation, ainsi que les étapes de déploiement et de maintenance du projet. +L'application permet de consulter les principales informations du service Orange County Lettings à travers une interface web développée avec Django. -## Fonctionnalités +Les fonctionnalités disponibles sont les suivantes : -L’application vise à fournir une interface simple de consultation de contenus immobiliers, structurée autour de deux modules principaux : les annonces et les profils utilisateurs. +- consultation des annonces immobilières ; +- consultation des profils utilisateurs ; +- accès à l'interface d'administration Django pour la gestion des données. -### Consultation des annonces immobilières -- Affichage de la liste des annonces disponibles (lettings) -- Consultation du détail d’une annonce (adresse complète et informations associées) +## Architecture -### Gestion des profils utilisateurs -- Affichage de la liste des profils utilisateurs -- Consultation du détail d’un profil (nom d’utilisateur et informations associées) +Le projet est organisé selon une architecture Django modulaire composée de trois applications principales : -### Page d’accueil -- Page d’entrée de l’application présentant une navigation vers les principales sections +- **oc_lettings_site** : configuration générale du projet (settings, routage, serveur WSGI) ; +- **lettings** : gestion des annonces immobilières ; +- **profiles** : gestion des profils utilisateurs. -### Interface d’administration -- Accès à l’interface d’administration Django -- Gestion des données (profils et annonces) via l’admin intégré +Cette organisation permet de séparer les responsabilités de chaque composant et facilite la maintenance ainsi que l'évolution de l'application. -## Architecture globale +L'application est conteneurisée avec Docker et déployée automatiquement via une pipeline CI/CD sur la plateforme Render. -L’application Orange County Lettings repose sur une architecture Django modulaire organisée autour de plusieurs composants indépendants. +Pour une description détaillée de l'architecture, du déploiement et de l'infrastructure, consultez la documentation complète du projet. -### Architecture applicative Django Le projet est structuré en trois éléments principaux : -- oc_lettings_site : projet Django principal contenant la configuration globale (settings, urls, wsgi) ainsi que les ressources communes. -- lettings : application métier dédiée à la gestion des annonces immobilières. -- profiles : application métier dédiée à la gestion des profils utilisateurs. +## Stack technique -Cette séparation permet une meilleure modularité et une évolution indépendante des fonctionnalités. +Le projet s'appuie sur les technologies suivantes : -### Architecture de déploiement L’application est conçue pour fonctionner dans un environnement conteneurisé et automatisé : -- L’application est exécutée dans un conteneur Docker. -- L’image Docker est construite et versionnée via une pipeline CI/CD. -- Les images sont stockées sur DockerHub. -- Le déploiement est automatisé sur la plateforme Render. +- **Langage :** Python 3.10 +- **Framework :** Django 3.0 +- **Bases de données :** SQLite (développement rapide), PostgreSQL (Docker et production) +- **Conteneurisation :** Docker, Docker Compose +- **CI/CD :** GitHub Actions +- **Tests et qualité :** pytest, pytest-django, coverage, flake8, black +- **Monitoring :** Sentry +- **Production :** Gunicorn, WhiteNoise, Render +- **Configuration :** python-dotenv -### Base de données -L'application s'appuie sur une stratégie de gestion des données adaptée aux différents environnements d'exécution (développement, intégration continue et production). Cette approche permet de concilier simplicité d'utilisation, reproductibilité des environnements et cohérence avec la production. +## Utilisation en local -La configuration de la base de données est gérée via des variables d’environnement, permettant d’adapter automatiquement le comportement de l’application selon le contexte d’exécution. +### Prérequis -Les technologies employées et leur répartition sont détaillées dans la section Stack technique. +Pour exécuter le projet localement, les outils suivants sont nécessaires : -### Vue d’ensemble du flux applicatif -Code source → GitHub Actions → Docker Image → DockerHub → Render → Application déployée avec PostgreSQL +- Git ; +- Python 3.10 ou supérieur ; +- Docker et Docker Compose (optionnel, pour une exécution conteneurisée). -## Stack technique +### Installation -### Langage et framework principal -- Python 3.10 : langage principal du projet -- Django 3.0 : framework web utilisé pour structurer l’application selon une architecture MVC +Clonez le dépôt puis installez les dépendances dans un environnement virtuel : -### Base de données -- SQLite : utilisé pour les tests et scénarios de démarrage rapide -- PostgreSQL : utilisé en développement (Docker Compose) et en production (Render) +```bash +git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git +cd Python-OC-Lettings-FR -### Conteneurisation -- Docker : containerisation de l’application -- Docker Compose : orchestration des services en environnement de développement -- DockerHub : registre d’images Docker +python -m venv venv +source venv/bin/activate # Linux / macOS +# ou +venv\Scripts\activate # Windows -### CI/CD -- GitHub Actions : pipeline d’intégration et de déploiement continus -- Automatisation des étapes de test, linting, build et déploiement - -### Qualité de code et tests -- pytest / pytest-django : tests unitaires de l’application Django -- coverage : mesure de la couverture de tests -- flake8 : analyse statique du code (PEP8) -- black : formattage automatique du code - -### Monitoring -- sentry-sdk : monitoring des erreurs en production - -### Production -- Render : plateforme d’hébergement et de déploiement automatisé -- Gunicorn : serveur WSGI utilisé pour l’exécution de l’application en environnement de production -- whitenoise : gestion des fichiers statiques en production - - -### Variables d’environnement -- python-dotenv : gestion des variables d’environnement en développement - -## Usage local - -### Prérequis - -Avant de lancer l'application, assurez-vous que les outils suivants sont installés sur votre machine : -- Compte GitHub avec accès en lecture à ce repository -- Git CLI pour cloner le dépôt et gérer le code source. -- Interpréteur Python (version 3.10 recomandée pour des raisons de compatibilité) uniquement si vous souhaitez exécuter l'application hors conteneur ou contribuer au développement. -- Docker (incluant Docker Compose) : pour exécuter l'application dans un environnement conteneurisé. - -Dans le reste de la documentation sur le développement local, il est supposé que la commande python de votre OS shell exécute l'interpréteur Python ci-dessus (à moins qu'un environnement virtuel ne soit activé). - -### Cloner le repository - -- `cd /path/to/put/project/in ` -- `git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git` - -### Créer l'environnement virtuel - -Pour le développement local, il est recommandé d'utiliser un environnement virtuel Python : -- `cd /path/to/Python-OC-Lettings-FR ` -- `python -m venv venv` -- `apt-get install python3-venv` (Si l'étape précédente comporte des erreurs avec un paquet non trouvé sur Ubuntu) -- Activer l'environnement: Linux / macOS: `source venv/bin/activate` ou `.\venv\Scripts\Activate.ps` pour Windows (PowerShell) -- Confirmer que la commande python exécute l'interpréteur Python dans l'environnement virtuel `which python ` -- Confirmer que la version de l'interpréteur Python est la version 3.6 ou supérieure `python --version ` -- Confirmer que la commande pip exécute l'exécutable pip dans l'environnement virtuel, `which pip ` -- Pour désactiver l'environnement, `deactivate ` - -Les sections suivantes présentent les différentes méthodes d'exécution de l'application, selon que vous souhaitiez simplement la découvrir ou contribuer à son développement. - -### Exécuter le site (quick start) -- `cd /path/to/Python-OC-Lettings-FR ` -- `source venv/bin/activate ` -- `pip install --requirement requirements.txt ` -- `python manage.py runserver ` -- Aller sur http://localhost:8000 dans un navigateur. -- Confirmer que le site fonctionne et qu'il est possible de naviguer (vous devriez voir plusieurs profils et locations). - -### Développement avec Docker Compose - -Pour reproduire un environnement proche de la production, le projet fournit une configuration Docker Compose permettant d'exécuter simultanément l'application Django et une base de données PostgreSQL. - -### Configuration - -Avant le premier lancement, créez un fichier `.env` à la racine du projet. - -Ce fichier contient les variables d'environnement nécessaires à la configuration de l'application et de la base de données. Pour des raisons de sécurité, il n'est pas versionné dans le dépôt Git. - -Les variables suivantes sont notamment requises : - -* `DEBUG`=False -* `USE_SQLITE`=False -* `POSTGRES_DB`=oc_lettings -* `POSTGRES_USER`=oc_user -* `POSTGRES_HOST`=db -* `POSTGRES_PORT`=5432 +pip install -r requirements.txt +``` -Les variables suivantes contiennent des informations sensibles. Leurs valeurs ne sont pas versionnées dans le dépôt Git et doivent être fournies séparément. +Créez ensuite un fichier .env à la racine du projet et renseignez les variables d'environnement nécessaires. -* `SECRET_KEY` -* `POSTGRES_PASSWORD` -* `SENTRY_DSN` +### Lancement de l'application -### Démarrage de l'environnement +#### Avec Django -Une fois le fichier `.env` créé, démarrez les services à l'aide de Docker Compose : +```bash +python manage.py migrate +python manage.py runserver +``` +#### Avec Docker Compose +```bash docker compose up --build - -Au démarrage, le conteneur de l'application applique automatiquement les migrations de la base de données avant de lancer le serveur Gunicorn. +``` L'application est ensuite accessible à l'adresse : http://localhost:8000 -Les services sont exécutés au premier plan. Pour arrêter l'environnement, utilisez Ctrl + C ou lancez Docker Compose en mode détaché avec docker compose up -d. - -### Données de démonstration - -L'environnement de développement démarre avec une base PostgreSQL vide. - -Si vous souhaitez disposer d'un jeu de données d'exemple, les fixtures fournies avec le projet peuvent être chargées à l'aide de la commande `loaddata`. Cette procédure est décrite dans la documentation technique. +Pour plus d'informations sur la configuration de l'environnement, les variables d'environnement ou l'utilisation de Docker Compose, consultez la documentation complète. ## Tests et qualité du code -Le projet intègre plusieurs outils destinés à garantir la qualité du code et le bon fonctionnement de l'application. Les commandes ci-dessous sont à exécuter depuis la racine du projet, après activation de l'environnement virtuel : - -- `cd /path/to/Python-OC-Lettings-FR` -- `source venv/bin/activate` - -### Linting - -La conformité du code aux règles de style (PEP 8) peut être vérifiée à l'aide de la commande suivante : - -- `flake8` +Les principaux outils de qualité peuvent être exécutés depuis la racine du projet : -### Tests unitaires - -Les tests unitaires sont exécutés avec `pytest` : - -- `pytest` - -### Couverture de tests - -La couverture des tests peut être mesurée avec : - -- `pytest --cov` +```bash +flake8 # Analyse statique +pytest # Tests unitaires +pytest --cov # Couverture des tests +``` -Ces vérifications sont exécutées automatiquement par la pipeline d'intégration continue à chaque `push` et à chaque ouverture ou mise à jour d'une Pull Request. Les différentes étapes de cette pipeline sont détaillées dans la section suivante. +Ces vérifications sont également exécutées automatiquement par la pipeline CI/CD à chaque `push` et à chaque Pull Request. ## Pipeline CI/CD -Le projet intègre une pipeline d'intégration et de déploiement continus (CI/CD) mise en œuvre avec GitHub Actions. - -À chaque `push` ou ouverture de Pull Request, la pipeline exécute automatiquement les étapes nécessaires pour vérifier la qualité du code. Lorsqu'une modification est intégrée à la branche `master`, elle poursuit le processus jusqu'à la construction de l'image Docker et au déploiement automatique de l'application sur Render. - -### Vue d'ensemble +Le projet utilise une pipeline GitHub Actions pour automatiser les contrôles qualité, la construction de l'image Docker et le déploiement en production. ```text Push / Pull Request @@ -248,129 +144,21 @@ Job "compile" Render ``` -### Job `compile` - -Ce premier job est exécuté à chaque `push` et à chaque Pull Request. - -Il réalise les opérations suivantes : - -* récupération du dépôt Git ; -* installation de Python 3.10 ; -* installation des dépendances du projet ; -* exécution de l'analyse statique avec `flake8` ; -* exécution des tests unitaires avec `pytest` ; -* vérification d'un seuil minimal de 80 % de couverture de tests. +Les informations sensibles utilisées par la pipeline sont stockées dans les **GitHub Actions Secrets** et ne sont jamais versionnées dans le dépôt. -Le job compile s'exécute dans un environnement GitHub Actions éphémère. À chaque exécution, une nouvelle machine virtuelle est provisionnée, les dépendances du projet sont installées, puis les opérations de linting et de tests sont exécutées. Une fois le job terminé, cet environnement est automatiquement détruit, garantissant des exécutions indépendantes et reproductibles sans impact sur les environnements de développement ou de production. +## Déploiement -Pour cette étape, l'application est configurée pour utiliser SQLite afin de disposer d'un environnement de test léger et reproductible. +L'application est déployée automatiquement sur **Render** à partir de la pipeline GitHub Actions. -### Job `containerize` +Lorsqu'un changement est fusionné sur la branche `master`, la pipeline : -Ce job est exécuté uniquement lors d'un `push` sur la branche `master`. +1. construit une image Docker ; +2. publie cette image sur Docker Hub ; +3. déclenche le déploiement sur Render via un Deploy Hook. -Après authentification auprès de DockerHub, il : +Au démarrage du conteneur, le script `start.sh` applique automatiquement les migrations Django avant de lancer le serveur Gunicorn. -* construit l'image Docker de l'application ; -* applique deux tags (`latest` et le SHA du commit) ; -* publie les deux images sur DockerHub. - -Cette stratégie permet de disposer à la fois d'une image représentant la dernière version stable et d'une image correspondant exactement à un commit donné. - -### Job `deploy` - -Une fois l'image publiée, le dernier job déclenche automatiquement le **Deploy Hook** de Render. - -Render télécharge alors la dernière image disponible sur DockerHub et redéploie l'application sans intervention manuelle. - -### Gestion des secrets - -Les informations sensibles utilisées par la pipeline sont stockées dans les **GitHub Actions Secrets**. - -Cette approche permet de ne jamais versionner les secrets dans le dépôt Git tout en les rendant accessibles aux différentes étapes de la pipeline. - -Les secrets utilisés comprennent notamment : - -* `SECRET_KEY` -* `SENTRY_DSN` -* `DOCKER_USERNAME` -* `DOCKER_PASSWORD` -* `RENDER_DEPLOY_HOOK` - - -Cette architecture permet de garantir qu'aucun déploiement en production n'est réalisé tant que les étapes de validation (linting, tests unitaires et couverture de code) n'ont pas été exécutées avec succès. - -## Déploiement sur Render - -L’application est déployée automatiquement sur la plateforme **Render**, qui héberge le service web ainsi que la base de données PostgreSQL en environnement de production. - -Le déploiement est entièrement automatisé et s’appuie sur la pipeline CI/CD ainsi que sur une architecture conteneurisée via Docker. - -### Architecture de déploiement - -Le déploiement repose sur le flux suivant : - -- GitHub Actions construit et publie une image Docker sur DockerHub -- Render récupère automatiquement cette image via un **Deploy Hook** -- L’application est redéployée sans intervention manuelle - -### Service web Render - -Le service web Render est configuré pour : - -- exécuter un conteneur Docker basé sur l’image publiée sur DockerHub -- exposer l’application via un port dynamique fourni par Render -- exécuter automatiquement le script `start.sh` au démarrage - -### Script de démarrage (`start.sh`) - -Au lancement du conteneur, le script `start.sh` est exécuté automatiquement. - -Il effectue les opérations suivantes : - -- application des migrations Django sur la base de données PostgreSQL -- lancement du serveur WSGI via Gunicorn - -Ce mécanisme garantit que la base de données est toujours synchronisée avec le schéma de l’application au moment du déploiement. - -### Base de données PostgreSQL - -En production, l’application utilise une base de données PostgreSQL managée par Render. Les informations de connexion sont fournies via des variables d’environnement injectées directement dans le service Render. Ces variables ne sont jamais versionnées dans le dépôt Git. - -### Variables d’environnement - -Render gère la configuration de l’application via des variables d’environnement définies dans son interface. - -Elles permettent notamment de : - -- Configurer la connexion à la base de données PostgreSQL -- Activer ou désactiver le mode debug -- Fournir les clés de services externes (Sentry, etc.) - -### Healthcheck - -Render effectue un **healthcheck HTTP** sur l’application afin de vérifier que le service répond correctement après déploiement. - -Ce mécanisme permet de s’assurer que : - -- le conteneur démarre correctement -- le serveur Gunicorn est opérationnel -- l’application est accessible avant d’exposer le service - -En cas d’échec du healthcheck, le déploiement est considéré comme non valide. - -### Déclenchement du déploiement - -Le déploiement est automatiquement déclenché lorsqu’un commit (ou une pull request) est validé(e) sur la branche principale (master). - -Le job deploy de la pipeline CI/CD envoie une requête HTTP au Deploy Hook Render, ce qui provoque : - -- La récupération de la dernière image Docker -- Le redémarrage du service -- Exécution automatique du healthcheck -- La mise en production immédiate des modifications si le healthcheck est un succès - -Une fois le déploiement terminé, l’application est accessible publiquement via l’URL fournie par Render. +L'application s'exécute en production avec une base de données PostgreSQL managée par Render. L'ensemble de la configuration est assuré au moyen de variables d'environnement et le service est vérifié automatiquement par le mécanisme de **Health Check** de Render. ## Autres commandes utiles @@ -399,13 +187,6 @@ Les documentations officielles suivantes permettent d'approfondir les technologi - GitHub Actions : https://docs.github.com/fr/actions - Render : https://render.com/docs -## Documentation complète - -La documentation technique du projet est générée avec **Sphinx** et publiée via **Read the Docs**. - -Elle est accessible ici : -👉 - ## Auteur Jérémy Muller, étudiant en développement applicatif python chez OpenClassrooms. From 05ee02650d2bdd6707a1739a3e2489c217094a5c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Mon, 6 Jul 2026 19:03:08 +0200 Subject: [PATCH 101/113] Update sphinx documentation --- docs/source/architecture.rst | 34 ++++++++++++++++++++ docs/source/index.rst | 1 - docs/source/installation.rst | 52 +++++++++++------------------- docs/source/introduction.rst | 39 +++++++++++------------ docs/source/local_usage.rst | 62 ------------------------------------ docs/source/technologies.rst | 48 ++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 118 deletions(-) create mode 100644 docs/source/architecture.rst delete mode 100644 docs/source/local_usage.rst create mode 100644 docs/source/technologies.rst diff --git a/docs/source/architecture.rst b/docs/source/architecture.rst new file mode 100644 index 0000000000..e6bf2464c8 --- /dev/null +++ b/docs/source/architecture.rst @@ -0,0 +1,34 @@ +Architecture +============ + +Orange County Lettings is organized as a modular Django project. Each application +is responsible for a specific functional domain, which improves maintainability +and simplifies future developments. + +Project structure +----------------- + +The project is composed of three main Django applications: + +- ``oc_lettings_site``: global project configuration (settings, URL routing, WSGI application and shared resources); +- ``lettings``: management of property listings; +- ``profiles``: management of user profiles. + +This separation follows Django's recommended application architecture and keeps +business logic isolated from project configuration. + +Source code documentation +------------------------- + +The following sections are automatically generated from the project's docstrings +using the Sphinx ``autodoc`` extension. + +They provide detailed information about the available modules, classes, +functions and views without duplicating the source code documentation. + +.. toctree:: + :maxdepth: 2 + + architecture/oc_lettings_site + architecture/lettings + architecture/profiles \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index ea3b961c19..bb6b554c9d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,7 +19,6 @@ Use the navigation menu below to browse each section. introduction installation - local_usage technologies architecture database diff --git a/docs/source/installation.rst b/docs/source/installation.rst index b516d519d9..1785e95e1a 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -3,24 +3,23 @@ Installation This section explains how to install and configure the Orange County Lettings project in a local environment. -Unlike the README, which focuses on quick usage, this section provides a more detailed explanation of the setup process and execution environments. +It complements the README by providing a more detailed setup procedure and environment configuration. The project supports two installation modes: -- Local Python environment (development) +- local Python environment (development) - Docker-based environment (production-like) Prerequisites ------------- -Before installing the project, ensure the following tools are available: +Ensure the following tools are installed: - Git - Python 3.10+ - pip - Docker and Docker Compose (optional but recommended) - Project cloning --------------- @@ -31,7 +30,6 @@ Clone the repository: git clone https://github.com/OpenClassrooms-Student-Center/Python-OC-Lettings-FR.git cd Python-OC-Lettings-FR - Local development setup ----------------------- @@ -40,41 +38,35 @@ This setup is intended for development without containers. Virtual environment ~~~~~~~~~~~~~~~~~~~ -A virtual environment isolates the project's Python dependencies from the global interpreter. - -Create a virtual environment: +Create and activate a virtual environment to isolate dependencies: .. code-block:: bash python -m venv venv -Activate it: - -.. code-block:: bash - # Linux / macOS source venv/bin/activate # Windows venv\Scripts\activate -Dependency installation -~~~~~~~~~~~~~~~~~~~~~~~ +Dependencies installation +~~~~~~~~~~~~~~~~~~~~~~~~~ -Install dependencies: +Install project dependencies: .. code-block:: bash pip install -r requirements.txt -This installs Django together with the libraries required for testing, code quality checks and production deployment. +This includes Django, testing tools, code quality utilities, and deployment dependencies. Environment configuration ------------------------- -The application relies on environment variables. +The application uses environment variables for configuration. -Create a `.env` file at the root of the project. +Create a `.env` file at the project root. Required variables: @@ -91,17 +83,15 @@ For PostgreSQL environments: - POSTGRES_HOST - POSTGRES_PORT - Database setup -------------- -Apply migrations before running the application: +Apply database migrations before starting the application: .. code-block:: bash python manage.py migrate - Run development server ---------------------- @@ -109,15 +99,14 @@ Run development server python manage.py runserver -Access: +Access the application at: http://localhost:8000 - Docker setup ------------- -The project also provides a Docker Compose configuration allowing the entire application stack to be started with a single command. +A Docker Compose configuration is provided to run the full application stack with a single command. This setup is recommended for: @@ -131,7 +120,6 @@ Start containers: docker compose up --build - Container initialization ------------------------ @@ -140,22 +128,18 @@ On startup, the container automatically: - applies database migrations - starts the Gunicorn server -This is handled by the `start.sh` script. - -This guarantees that the database schema is always synchronized before the application starts serving requests. - +This is handled by the `start.sh` script and ensures the database schema is always up to date before serving requests. Environment differences ----------------------- -- Local setup → SQLite (simpler, development-friendly) -- Docker setup → PostgreSQL (production-like) - +- Local setup: SQLite (lightweight, development-friendly) +- Docker setup: PostgreSQL (production-like) Summary ------- Two execution modes are available: -- Local environment → development and debugging -- Docker environment → production-like behavior \ No newline at end of file +- local environment for development and debugging +- Docker environment for production-like execution \ No newline at end of file diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 5a3430fe40..09578d1864 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -4,46 +4,43 @@ Introduction Project overview ---------------- -Orange County Lettings is a web application built with the Django framework. +Orange County Lettings is a Django-based web application that allows users to browse property listings and view detailed information about associated user profiles. -The application allows users to browse property listings and view detailed information about associated profiles. - -The project follows a modular architecture in order to clearly separate concerns between the different application components (users, listings, main site, etc.). +The project is designed with a modular architecture to ensure a clear separation of concerns between its different components (users, listings, main site, etc.). Development context ------------------- -This project was developed as part of the **Python Application Developer** training program provided by OpenClassrooms. +This project was developed as part of the Python Application Developer program at OpenClassrooms. -It serves as a practical implementation of key software engineering concepts, including: +It serves as a practical implementation of core software engineering concepts, including: -- web application development using Django -- modular project architecture design +- Django web application development +- modular architecture design - reproducible development environments -- continuous integration and deployment (CI/CD) -- production deployment automation +- CI/CD pipelines and automation Technical objectives -------------------- -Beyond functional implementation, this project aims to demonstrate the ability to manage a complete software development lifecycle: +Beyond functional requirements, this project demonstrates the ability to manage a complete software development lifecycle: -- source code management with Git and GitHub -- code quality enforcement using linting (flake8) +- Git and GitHub version control +- code quality enforcement with linting (flake8) - unit testing with pytest -- test coverage measurement -- automated checks using GitHub Actions +- test coverage analysis +- automated workflows using GitHub Actions - containerization with Docker - automated deployment to production environments High-level architecture ----------------------- -The application is composed of several main components: +The application consists of several core components: -- a Django web application handling business logic and user interface -- a relational database system, using SQLite in development/testing and PostgreSQL in production -- a CI/CD pipeline responsible for automated testing and deployment -- a containerized deployment infrastructure using Docker and Render +- a Django web application handling business logic and the user interface +- a relational database (SQLite in quickstart/testing, PostgreSQL in development production) +- a CI/CD pipeline for automated testing and deployment +- a containerized infrastructure using Docker and Render -The following sections of this documentation provide details on installation, usage, system architecture, and deployment procedures. \ No newline at end of file +The following sections of this documentation provide more details on installation, usage, architecture, and deployment. \ No newline at end of file diff --git a/docs/source/local_usage.rst b/docs/source/local_usage.rst deleted file mode 100644 index 1abe5a13ba..0000000000 --- a/docs/source/local_usage.rst +++ /dev/null @@ -1,62 +0,0 @@ -Local Usage -=========== - -This section describes the different ways to run the application in a local environment once it has been installed. - -It includes both a simple Python-based execution and a Docker Compose execution. - ---- - -Running locally with Django -------------------------------- - -.. code-block:: bash - - python -m venv venv - source venv/bin/activate - pip install -r requirements.txt - python manage.py migrate - python manage.py runserver - -Access the application at: - -http://localhost:8000 - -Running with Docker Compose ------------------------- - -The project provides a Docker Compose configuration intended to reproduce an -environment close to production. - -Before using this execution mode, ensure that: - -- Docker is installed and running on the host machine; -- Docker Compose is available; -- a valid ``.env`` file has been created at the root of the project. - -The ``docker compose up --build`` command performs several operations: - -- builds the application image from the project's Dockerfile; -- creates a dedicated Docker network; -- starts a PostgreSQL container; -- starts the Django application container; -- automatically executes the ``start.sh`` initialization script. - -.. code-block:: bash - - docker compose up --build - -During startup, the application container automatically applies the database -migrations before launching the Gunicorn server. - -Once both containers are running, the application is available at: - -:: - - http://localhost:8000 - ---- - -Both execution modes rely on the same application codebase. - -The Django development server is generally preferred during development because it provides automatic code reloading, whereas Docker Compose offers an environment closer to production. \ No newline at end of file diff --git a/docs/source/technologies.rst b/docs/source/technologies.rst new file mode 100644 index 0000000000..c0bfc274a2 --- /dev/null +++ b/docs/source/technologies.rst @@ -0,0 +1,48 @@ +Technologies +============ + +The Orange County Lettings project is built on a modern Python web stack designed for scalability, maintainability, and deployment readiness. + +Backend +------- + +- Python 3.10: main programming language +- Django 3.0: web framework used for application structure, routing, and ORM + +Databases +---------- + +- SQLite: used for CI unit tests and fast setup +- PostgreSQL: used in Docker and production environments + +Containerization +----------------- + +- Docker: container runtime for the application +- Docker Compose: orchestration of multi-service environments + +CI/CD +----- + +- GitHub Actions: automated workflows for testing, linting, and deployment + +Testing and code quality +------------------------ + +- pytest / pytest-django: unit and integration testing +- coverage: test coverage measurement +- flake8: linting for code style and quality +- black: code formatting + +Production stack +---------------- + +- Gunicorn: WSGI HTTP server for running Django in production +- WhiteNoise: static file serving without external storage +- Render: cloud platform used for deployment + +Monitoring and configuration +----------------------------- + +- Sentry: error tracking and monitoring +- python-dotenv: environment variable management via `.env` files \ No newline at end of file From 23df1c3e0cdba7f9105e1b6b3098bf3a4b542544 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 7 Jul 2026 12:57:48 +0200 Subject: [PATCH 102/113] Reviewed docstrings for future autodoc usage. --- lettings/models.py | 66 ++++++++++++++---------------------- lettings/urls.py | 10 ++---- lettings/views.py | 37 +++++++++----------- oc_lettings_site/settings.py | 35 ++++--------------- oc_lettings_site/urls.py | 14 +++----- oc_lettings_site/views.py | 36 ++++++++++---------- profiles/models.py | 34 +++++++++---------- profiles/urls.py | 10 ++---- profiles/views.py | 41 ++++++++++------------ 9 files changed, 110 insertions(+), 173 deletions(-) diff --git a/lettings/models.py b/lettings/models.py index 4c465bd4b4..0fecba9664 100644 --- a/lettings/models.py +++ b/lettings/models.py @@ -1,16 +1,8 @@ """ -Database models for the lettings application. +Database models for the ``lettings`` application. -This module defines the core data structures used to represent -rental properties and their associated addresses. - -Two main models are provided: - -- Address: Represents a physical location with validation constraints. -- Letting: Represents a rental unit associated with a unique address. - -These models are mapped to dedicated database tables and enforce -basic validation rules at the model level. +This module defines the data models used to represent rental properties +and their associated addresses. """ from django.db import models @@ -19,19 +11,11 @@ class Address(models.Model): """ - Represents a physical address associated with a letting. - - The Address model stores structured location data and enforces - validation rules on numerical and string-based fields to ensure - consistency of stored information. - - Attributes: - number (PositiveIntegerField): Street number (maximum 4 digits). - street (CharField): Street name (maximum 64 characters). - city (CharField): City name (maximum 64 characters). - state (CharField): Two-character state code (minimum length enforced). - zip_code (PositiveIntegerField): Postal code (maximum 5 digits). - country_iso_code (CharField): ISO country code (3 characters minimum). + Represent the physical location of a rental property. + + This model stores the address associated with a letting and + enforces basic validation rules on its fields to ensure data + consistency. """ number = models.PositiveIntegerField(validators=[MaxValueValidator(9999)]) @@ -43,16 +27,19 @@ class Address(models.Model): def __str__(self) -> str: """ - Return a human-readable representation of the address. + Return a readable representation of the address. - Returns: - str: A formatted string combining street number and street name. + :returns: Street number followed by the street name. + :rtype: str """ return f"{self.number} {self.street}" class Meta: """ - Metadata configuration for the Address model. + Define metadata associated with the ``Address`` model. + + The explicit database table name preserves compatibility with the + original project schema. """ db_table = "lettings_address" @@ -62,15 +49,11 @@ class Meta: class Letting(models.Model): """ - Represents a rental property. - - The Letting model links a rental unit to a unique Address instance - using a one-to-one relationship. Each letting corresponds to exactly - one physical address. + Represent a rental property available through the application. - Attributes: - title (CharField): Name or title of the letting (maximum 256 characters). - address (OneToOneField): Unique associated Address instance. + Each letting is associated with a unique ``Address`` instance, + allowing the application to separate property information from + location data. """ title = models.CharField(max_length=256) @@ -78,16 +61,19 @@ class Letting(models.Model): def __str__(self): """ - Return a human-readable representation of the letting. + Return the title of the letting. - Returns: - str: The lettings title. + :returns: Letting title. + :rtype: str """ return self.title class Meta: """ - Metadata configuration for the Letting model. + Define metadata associated with the ``Letting`` model. + + The explicit database table name preserves compatibility with the + original project schema. """ db_table = "lettings_letting" diff --git a/lettings/urls.py b/lettings/urls.py index 4bc6ba961a..1c3a30a495 100644 --- a/lettings/urls.py +++ b/lettings/urls.py @@ -1,12 +1,8 @@ """ -URL configuration for the lettings application. +URL configuration for the ``lettings`` application. -This module defines the URL patterns associated with the lettings app. -It maps URL paths to their corresponding view functions responsible -for rendering letting listings and individual letting details. - -The `app_name` variable enables namespaced URL resolution within -the Django project. +This module maps URL patterns to their corresponding view functions +and defines the namespace used for URL resolution. """ from django.urls import path diff --git a/lettings/views.py b/lettings/views.py index 694ef3e12c..ff08775cea 100644 --- a/lettings/views.py +++ b/lettings/views.py @@ -1,11 +1,8 @@ """ -Views for the lettings application. +Views for the ``lettings`` application. -This module defines view functions responsible for displaying -the list of available lettings and the details of a specific letting. - -Each view retrieves data from the database using the Letting model -and renders the appropriate HTML template with a context dictionary. +This module provides the views responsible for displaying the list +of available lettings and the details of a specific letting. """ from django.shortcuts import render, get_object_or_404 @@ -19,16 +16,16 @@ def lettings_index(request: HttpRequest) -> HttpResponse: """ - Display the list of all available lettings. + Render the page listing all available lettings. - This view retrieves all Letting instances from the database - and renders them using the ``lettings/index.html`` template. + This view retrieves all lettings from the database and displays them + on the lettings index page. - This view logs access attempts for monitoring purposes. + An informational log entry is generated whenever the page is accessed. - :param request: The HTTP request object. + :param request: Incoming HTTP request. :type request: HttpRequest - :return: Rendered HTML page displaying the list of lettings. + :returns: Rendered lettings index page. :rtype: HttpResponse """ logger.info( @@ -45,21 +42,19 @@ def lettings_index(request: HttpRequest) -> HttpResponse: def letting(request: HttpRequest, letting_id: int) -> HttpResponse: """ - Display the details of a specific letting. + Render the page displaying details of a specific letting. - This view retrieves a single Letting instance based on its - primary key and renders its details using the - ``lettings/letting.html`` template. + This view retrieves a letting by its unique identifier and displays + its details on the letting detail page. - This view logs access attempts for monitoring purposes. + An informational log entry is generated whenever the page is accessed. - :param request: The HTTP request object. + :param request: Incoming HTTP request. :type request: HttpRequest - :param letting_id: The unique identifier of the letting. + :param letting_id: Unique identifier of the letting. :type letting_id: int - :return: Rendered HTML page displaying the letting details. + :returns: Rendered letting detail page. :rtype: HttpResponse - :raises Http404: If no letting matches the given ID. """ logger.info( "Letting detail accessed", diff --git a/oc_lettings_site/settings.py b/oc_lettings_site/settings.py index 2a982a01fc..d332c1290e 100644 --- a/oc_lettings_site/settings.py +++ b/oc_lettings_site/settings.py @@ -1,35 +1,12 @@ """ -Django settings for the oc_lettings_site project. +Application settings for the Orange County Lettings project. -This configuration file defines the settings used to run the application -across development, testing and production environments. +This module defines the Django configuration shared across development, +testing and production environments. -Main features include: - -Django application registration -Middleware configuration -Template configuration -Environment-based database selection (SQLite or PostgreSQL) -Authentication and password validation -Internationalization settings -Static files management with WhiteNoise -Error monitoring with Sentry -Environment variable configuration through .env files - -Database strategy: - -SQLite is used for local development and CI testing when -USE_SQLITE is enabled. -PostgreSQL is used for containerized and production deployments. - -Static assets are collected during the Docker image build process -and served through WhiteNoise. - -Sensitive settings such as credentials, secret keys and deployment -configuration are injected through environment variables. - -This file is designed to support both local development and -production-ready deployments. +The configuration relies on environment variables to adapt the application's +behavior, including database selection, static file management, logging and +error monitoring. """ import os diff --git a/oc_lettings_site/urls.py b/oc_lettings_site/urls.py index 958af66e31..ae9daea95b 100644 --- a/oc_lettings_site/urls.py +++ b/oc_lettings_site/urls.py @@ -1,15 +1,11 @@ """ -URL configuration for the main Django project (oc_lettings_site). +Root URL configuration for the Orange County Lettings project. -This module defines the root URL patterns for the project, including: +This module defines the application's main URL routing, including the +home page, the Django administration interface and the URL configurations +of the ``lettings`` and ``profiles`` applications. -- The home page (`index`) -- Inclusion of the lettings app URLs -- Inclusion of the profiles app URLs -- Django admin interface - -It maps URL paths to the corresponding view functions or included -URLconfs. Namespacing is managed at the app level where necessary. +Custom handlers for HTTP 404 and 500 errors are also registered here. """ from django.contrib import admin diff --git a/oc_lettings_site/views.py b/oc_lettings_site/views.py index d08e8808f1..4f269be6d7 100644 --- a/oc_lettings_site/views.py +++ b/oc_lettings_site/views.py @@ -1,8 +1,8 @@ """ Views for the main Django project (oc_lettings_site). -This module contains view functions responsible for rendering -the templates of the project's main pages, such as the home page. +This module defines the views responsible for rendering the application's +home page and custom HTTP error pages. """ from django.shortcuts import render @@ -13,16 +13,17 @@ def index(request): """ - Render the home page of the Orange County Lettings site. + Render the application's home page. - This view handles requests to the root URL ('/') and returns - the main landing page. + This view handles requests to the root URL (``/``) and returns + the main landing page of the Orange County Lettings application. - An info log is recorded to track normal application traffic. + An informational log entry is generated each time the page is + successfully accessed. - :param request: The HTTP request object. + :param request: Incoming HTTP request. :type request: HttpRequest - :return: Rendered home page template. + :returns: Rendered home page. :rtype: HttpResponse """ logger.info("Homepage accessed") @@ -34,15 +35,15 @@ def page_not_found(request, exception): """ Render the custom 404 error page. - This view is used by Django when a requested URL does not exist. + This view is invoked when Django cannot resolve the requested URL. - A warning log is recorded to track invalid navigation attempts. + A warning is logged to record the requested path and HTTP method. - :param request: The HTTP request object. + :param request: Incoming HTTP request. :type request: HttpRequest - :param exception: The exception raised by the resolver. + :param exception: URL resolution exception. :type exception: Exception - :return: Rendered 404 error page with HTTP status 404. + :returns: Rendered 404 error page. :rtype: HttpResponse """ logger.warning("404 error encountered", extra={"path": request.path, "method": request.method}) @@ -53,14 +54,13 @@ def server_error(request): """ Render the custom 500 error page. - This view is used by Django when an unhandled server error occurs. + This view is called whenever an unhandled server error occurs. - An error log is recorded to capture critical failures for - monitoring and debugging purposes. + An error is logged to facilitate monitoring and post-mortem debugging. - :param request: The HTTP request object. + :param request: Incoming HTTP request. :type request: HttpRequest - :return: Rendered 500 error page with HTTP status 500. + :returns: Rendered 500 error page. :rtype: HttpResponse """ logger.error("500 error encountered", extra={"path": request.path, "method": request.method}) diff --git a/profiles/models.py b/profiles/models.py index 265a146457..653d1185f7 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -1,9 +1,8 @@ """ -Models for the profiles application. +Database models for the ``profiles`` application. -This module defines the Profile model, which extends the built-in -Django User model with additional application-specific data, -such as the user's favorite city. +This module defines the data model used to associate additional +profile information with Django's built-in authentication system. """ from django.db import models @@ -12,14 +11,13 @@ class Profile(models.Model): """ - Profile model extending Django's built-in User. - - Attributes: - user (OneToOneField): A one-to-one relationship with the User model, - ensuring each user has a single profile. The related_name - "new_user" allows reverse access from User instances. - favorite_city (CharField): Optional field storing the user's - favorite city, with a maximum length of 64 characters. + Store additional information associated with a Django user. + + Each profile is linked to a single ``User`` instance through a + one-to-one relationship and currently stores the user's favorite city. + + The model extends Django's authentication system without modifying + the built-in ``User`` model. """ user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="new_user") @@ -27,18 +25,18 @@ class Profile(models.Model): def __str__(self): """ - Returns a string representation of the Profile instance. + Return the username associated with this profile. - Returns: - str: The username of the associated User. + :returns: Username of the related user. + :rtype: str """ return self.user.username class Meta: """ - Metadata for the Profile model. + Define metadata associated with the ``Profile`` model. - Attributes: - db_table (str): Explicit database table name for the model. + The explicit database table name preserves compatibility with the + original project schema. """ db_table = "profiles_profile" diff --git a/profiles/urls.py b/profiles/urls.py index 2efde1d451..0cf4efc226 100644 --- a/profiles/urls.py +++ b/profiles/urls.py @@ -1,12 +1,8 @@ """ -URL configuration for the profiles application. +URL configuration for the ``profiles`` application. -This module defines the URL patterns associated with the profiles app. -It maps URL paths to their corresponding view functions responsible -for rendering profile listings and individual profile details. - -The `app_name` variable enables namespaced URL resolution within -the Django project. +This module maps the application's URL patterns to the corresponding +view functions and defines the namespace used for URL resolution. """ from django.urls import path diff --git a/profiles/views.py b/profiles/views.py index 784ea700ca..302d027daf 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -1,10 +1,8 @@ """ -Views module for the profiles application. +Views for the ``profiles`` application. -This module contains the view functions responsible for rendering -the profiles list and individual profile detail pages. -Each view retrieves data from the database and passes it to -the corresponding templates for rendering. +This module provides the views responsible for displaying the list +of user profiles and individual profile details. """ from django.shortcuts import render, get_object_or_404 @@ -17,18 +15,16 @@ def profiles_index(request): """ - Render a page displaying all profiles. + Render the page listing all user profiles. - Retrieves all Profile instances from the database and passes them - to the 'profiles/index.html' template under the context variable - 'profiles_list'. + This view handles requests to the profiles index page and displays + every available profile. - This view logs access events for monitoring purposes. + An informational log entry is generated whenever the page is accessed. - :param request: The HTTP request object. + :param request: Incoming HTTP request. :type request: HttpRequest - - :return: Rendered HTML page with the list of profiles. + :returns: Rendered profiles index page. :rtype: HttpResponse """ @@ -46,23 +42,20 @@ def profiles_index(request): def profile(request, username): """ - Render a page displaying details of a specific profile. + Render the detail page of a user profile. - Retrieves a Profile instance from the database corresponding - to the provided username and passes it to the - 'profiles/profile.html' template under the context variable 'profile'. + This view retrieves the profile associated with the provided username + and displays its information. - This view logs access events for monitoring purposes. + An informational log entry is generated whenever a profile is viewed. - :param request: The HTTP request object. + :param request: Incoming HTTP request. :type request: HttpRequest - :param username: The username of the user whose profile is requested. + :param username: Username identifying the requested profile. :type username: str - - :return: Rendered HTML page with the profile details. + :returns: Rendered profile detail page. :rtype: HttpResponse - - :raises Http404: If no profile matches the given username. + :raises Http404: If no matching profile exists. """ logger.info( From 77f390fb676e7599e2bee88cfa74f43e11980587 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 7 Jul 2026 12:58:16 +0200 Subject: [PATCH 103/113] First setup readthedocs ready --- docs/source/architecture.rst | 55 ++++++++++++++++++++--------- docs/source/conf.py | 9 +++++ docs/source/database.rst | 54 ++++++++++++++++++++++++++++ docs/source/deployment.rst | 49 +++++++++++++++++++++++++ docs/source/index.rst | 1 - docs/source/views_and_endpoints.rst | 45 +++++++++++++++++++++++ readthedocs.yml | 13 +++++++ 7 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 docs/source/database.rst create mode 100644 docs/source/deployment.rst create mode 100644 docs/source/views_and_endpoints.rst create mode 100644 readthedocs.yml diff --git a/docs/source/architecture.rst b/docs/source/architecture.rst index e6bf2464c8..b5286ba4ee 100644 --- a/docs/source/architecture.rst +++ b/docs/source/architecture.rst @@ -2,33 +2,54 @@ Architecture ============ Orange County Lettings is organized as a modular Django project. Each application -is responsible for a specific functional domain, which improves maintainability -and simplifies future developments. +is responsible for a specific functional domain, improving maintainability and +allowing independent evolution of the different components. Project structure ----------------- The project is composed of three main Django applications: -- ``oc_lettings_site``: global project configuration (settings, URL routing, WSGI application and shared resources); -- ``lettings``: management of property listings; -- ``profiles``: management of user profiles. +- ``oc_lettings_site``: global project configuration, URL routing, WSGI application + and shared settings; +- ``lettings``: management of property listings and associated data; +- ``profiles``: management of user profiles and user-related information. -This separation follows Django's recommended application architecture and keeps +This separation follows Django's recommended application structure and keeps business logic isolated from project configuration. -Source code documentation -------------------------- +Application architecture +------------------------ -The following sections are automatically generated from the project's docstrings -using the Sphinx ``autodoc`` extension. +The application follows Django's Model-View-Template (MVT) architecture: -They provide detailed information about the available modules, classes, -functions and views without duplicating the source code documentation. +- **Models** define the data structure and database interactions through Django ORM; +- **Views** handle HTTP requests, retrieve data and prepare responses; +- **Templates** provide the user interface rendered by the application. -.. toctree:: - :maxdepth: 2 +Request flow +------------ - architecture/oc_lettings_site - architecture/lettings - architecture/profiles \ No newline at end of file +A typical request follows this workflow: + +1. The user sends an HTTP request. +2. Django resolves the URL through the project's routing configuration. +3. The corresponding view processes the request. +4. Models are used to retrieve or update data when required. +5. The view renders the appropriate template and returns an HTTP response. + +Deployment architecture +----------------------- + +The application is containerized using Docker and deployed through a CI/CD +pipeline. + +The production architecture relies on: + +- Docker for application containerization; +- PostgreSQL as the production database; +- Gunicorn as the WSGI server; +- Render as the hosting platform. + +The source code reference generated from docstrings is available in the +dedicated code reference section of this documentation. \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 2700780bb7..bf4f8624e2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,9 +5,17 @@ import os import sys +import django sys.path.insert(0, os.path.abspath("../..")) +os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", + "oc_lettings_site.settings", +) + +django.setup() + # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information @@ -20,6 +28,7 @@ extensions = [ "sphinx.ext.autodoc", + "sphinx.ext.napoleon", "sphinx.ext.viewcode", ] diff --git a/docs/source/database.rst b/docs/source/database.rst new file mode 100644 index 0000000000..67c1e9950f --- /dev/null +++ b/docs/source/database.rst @@ -0,0 +1,54 @@ +Database +======== + +The Orange County Lettings project uses a relational database system to store +application data. + +Django's Object-Relational Mapping (ORM) is used to interact with the database +through Python models instead of writing direct SQL queries. + +Database environments +--------------------- + +The project uses different database backends depending on the execution +environment: + +- **SQLite** is used for local development and testing. It provides a lightweight + database solution requiring no additional service configuration. +- **PostgreSQL** is used for Docker and production environments. It provides a + more robust and scalable database system suitable for deployment. + +The database engine is selected through environment configuration. + +Data models +----------- + +The application data is organized through Django models defined in each +application. + +Main entities include: + +- ``Letting``: represents a property listing; +- ``Address``: stores the location information associated with a letting; +- ``Profile``: stores additional information associated with a user. + +The models are managed through Django migrations, which ensure that the +database schema remains synchronized with the application code. + +Database reference +------------------ + +The following sections are automatically generated from the project's model +docstrings using Sphinx ``autodoc``. + +Lettings models +~~~~~~~~~~~~~~~ + +.. automodule:: lettings.models + :members: Address, Letting + +Profiles models +~~~~~~~~~~~~~~~ + +.. automodule:: profiles.models + :members: Profile \ No newline at end of file diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst new file mode 100644 index 0000000000..0d6e7d5a7d --- /dev/null +++ b/docs/source/deployment.rst @@ -0,0 +1,49 @@ +Deployment +========== + +Deployment overview +------------------- + +The application is deployed through an automated CI/CD pipeline. +The deployment process ensures that code quality checks and tests are +validated before releasing a new version. + +Continuous integration +---------------------- + +The CI pipeline is managed with GitHub Actions. + +The workflow performs: + +- dependency installation; +- code quality checks with flake8; +- automated tests with pytest; +- coverage measurement. + +Containerization +---------------- + +The application is containerized using Docker. + +The production container includes: + +- the Django application; +- Gunicorn as the WSGI server; +- PostgreSQL database connectivity. + +Production environment +---------------------- + +The application is deployed on Render. + +The production environment relies on: + +- PostgreSQL for persistent data storage; +- Gunicorn to serve the Django application; +- WhiteNoise for static files handling. + +Monitoring +---------- + +Application monitoring is provided through Sentry, which collects runtime +errors and helps identify production issues. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index bb6b554c9d..e96478adc3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,5 +23,4 @@ Use the navigation menu below to browse each section. architecture database views_and_endpoints - usage deployment diff --git a/docs/source/views_and_endpoints.rst b/docs/source/views_and_endpoints.rst new file mode 100644 index 0000000000..cbeb83e507 --- /dev/null +++ b/docs/source/views_and_endpoints.rst @@ -0,0 +1,45 @@ +Views and endpoints +=================== + +The application exposes HTTP endpoints through Django URL routing. +Each endpoint is associated with a view function responsible for processing +requests and returning the appropriate response. + +Request handling +---------------- + +Django processes incoming requests through the following workflow: + +1. The URL dispatcher matches the requested path with a configured route. +2. The associated view retrieves or processes the required data. +3. The view renders a template with the provided context. +4. Django returns the generated HTTP response to the client. + +URL organization +---------------- + +Each Django application defines its own URL configuration: + +- ``lettings`` manages property listing pages; +- ``profiles`` manages user profile pages. + +The application namespaces allow URL names to remain isolated between +different components. + +Views reference +--------------- + +The following sections are automatically generated from the project's +view docstrings using Sphinx ``autodoc``. + +Lettings views +~~~~~~~~~~~~~~ + +.. automodule:: lettings.views + :members: + +Profiles views +~~~~~~~~~~~~~~ + +.. automodule:: profiles.views + :members: \ No newline at end of file diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000000..57ff28d690 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.10" + +sphinx: + configuration: docs/source/conf.py + +python: + install: + - requirements: requirements.txt \ No newline at end of file From 5d1e864dab5792846b93a637fa95117bc0bf9cf7 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 7 Jul 2026 21:36:53 +0200 Subject: [PATCH 104/113] Added the last part of our sphinx documentation --- docs/source/index.rst | 1 + docs/source/use_cases.rst | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 docs/source/use_cases.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index e96478adc3..6821f22bdd 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,4 +23,5 @@ Use the navigation menu below to browse each section. architecture database views_and_endpoints + use_cases deployment diff --git a/docs/source/use_cases.rst b/docs/source/use_cases.rst new file mode 100644 index 0000000000..f3d6d6dcef --- /dev/null +++ b/docs/source/use_cases.rst @@ -0,0 +1,63 @@ +Use Cases +========= + +This section describes the main use cases supported by the Orange County +Lettings application. + +Functional use cases +-------------------- + +Browse rental listings +~~~~~~~~~~~~~~~~~~~~~~ + +Typical workflow: + +#. Open the application homepage. +#. Navigate to the lettings section. +#. Browse the list of available properties. +#. Select a listing to view its details. + +View a user profile +~~~~~~~~~~~~~~~~~~~ + +Users can access the details of a specific profile, including the associated +favorite city. + +Typical workflow: + +#. Open the profiles section. +#. Browse the available user profiles. +#. Select a profile from the list. +#. View the profile details. + +Manage application data +~~~~~~~~~~~~~~~~~~~~~~~ + +Administrators can manage lettings, addresses and user profiles through +the Django administration interface. + +Typical tasks include: + +- creating or updating lettings; +- managing addresses; +- editing user profiles; +- removing obsolete records. + +Development use case +-------------------- + +Contribute to the project +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Developers can contribute to the project by working in a reproducible +development environment, running automated quality checks and tests, and +submitting changes through the Git workflow. + + +A typical contribution follows the project's development workflow: + +#. Create a feature branch. +#. Implement the requested changes. +#. Push the branch to GitHub. +#. Let the CI pipeline validate the contribution. +#. Merge and deploy the application. \ No newline at end of file From 563cca83beb7132c43674c35188bfec269c0d874 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 7 Jul 2026 21:40:13 +0200 Subject: [PATCH 105/113] Deleted useless sphinx extension --- docs/source/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index bf4f8624e2..0e6de4dc74 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,7 +29,6 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", - "sphinx.ext.viewcode", ] templates_path = ['_templates'] From a707b5d4be3a8a7015225c562fdd81badf0b62d1 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 7 Jul 2026 23:07:42 +0200 Subject: [PATCH 106/113] Final documentation reviewing, including minor correction and separation off deployment.rst into two separates files with clear purposes. --- docs/source/architecture.rst | 8 +- docs/source/database.rst | 5 +- docs/source/deployment.rst | 65 +++++++------- docs/source/index.rst | 9 +- docs/source/installation.rst | 13 +-- docs/source/introduction.rst | 11 +-- docs/source/pipeline.rst | 126 ++++++++++++++++++++++++++++ docs/source/technologies.rst | 6 +- docs/source/use_cases.rst | 2 +- docs/source/views_and_endpoints.rst | 7 +- 10 files changed, 185 insertions(+), 67 deletions(-) create mode 100644 docs/source/pipeline.rst diff --git a/docs/source/architecture.rst b/docs/source/architecture.rst index b5286ba4ee..95bd9c766f 100644 --- a/docs/source/architecture.rst +++ b/docs/source/architecture.rst @@ -11,7 +11,7 @@ Project structure The project is composed of three main Django applications: - ``oc_lettings_site``: global project configuration, URL routing, WSGI application - and shared settings; + and project settings; - ``lettings``: management of property listings and associated data; - ``profiles``: management of user profiles and user-related information. @@ -41,7 +41,7 @@ A typical request follows this workflow: Deployment architecture ----------------------- -The application is containerized using Docker and deployed through a CI/CD +The application is containerized using Docker and deployed automatically through a CI/CD pipeline. The production architecture relies on: @@ -51,5 +51,5 @@ The production architecture relies on: - Gunicorn as the WSGI server; - Render as the hosting platform. -The source code reference generated from docstrings is available in the -dedicated code reference section of this documentation. \ No newline at end of file +The detailed source code reference generated from docstrings is available in the +dedicated API reference sections of this documentation. \ No newline at end of file diff --git a/docs/source/database.rst b/docs/source/database.rst index 67c1e9950f..7c7b0553dc 100644 --- a/docs/source/database.rst +++ b/docs/source/database.rst @@ -13,7 +13,7 @@ Database environments The project uses different database backends depending on the execution environment: -- **SQLite** is used for local development and testing. It provides a lightweight +- **SQLite** is used for quickstart and automated tests. It provides a lightweight database solution requiring no additional service configuration. - **PostgreSQL** is used for Docker and production environments. It provides a more robust and scalable database system suitable for deployment. @@ -30,7 +30,8 @@ Main entities include: - ``Letting``: represents a property listing; - ``Address``: stores the location information associated with a letting; -- ``Profile``: stores additional information associated with a user. +- ``Profile``: extends Django's built-in authentication system by +associating additional information with each user. The models are managed through Django migrations, which ensure that the database schema remains synchronized with the application code. diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst index 0d6e7d5a7d..2b77374dd8 100644 --- a/docs/source/deployment.rst +++ b/docs/source/deployment.rst @@ -1,49 +1,52 @@ Deployment ========== -Deployment overview -------------------- +The Orange County Lettings application is deployed on Render using a +containerized production environment. -The application is deployed through an automated CI/CD pipeline. -The deployment process ensures that code quality checks and tests are -validated before releasing a new version. +Production architecture +----------------------- -Continuous integration ----------------------- +The production environment relies on: -The CI pipeline is managed with GitHub Actions. +- Docker for application containerization; +- Render as hosting platform; +- PostgreSQL as persistent database; +- Gunicorn as WSGI server; +- WhiteNoise for static file serving. -The workflow performs: +Deployment process +------------------ -- dependency installation; -- code quality checks with flake8; -- automated tests with pytest; -- coverage measurement. +The deployment process is automated through the CI/CD pipeline. -Containerization ----------------- +When changes are merged into the production branch, the pipeline: -The application is containerized using Docker. +#. Builds a new Docker image. +#. Publishes the image to Docker Hub. +#.Triggers a deployment on Render using a Deploy Hook. +#. Starts the updated production container. -The production container includes: +The Render Deploy Hook allows the CI/CD pipeline to remotely trigger a new +deployment while keeping deployment credentials outside the source code. -- the Django application; -- Gunicorn as the WSGI server; -- PostgreSQL database connectivity. +Application startup +------------------- -Production environment ----------------------- +When the container starts: -The application is deployed on Render. +#. The environment variables are loaded. +#. Database migrations are applied through ``start.sh``. +#. Gunicorn starts the Django application. +#. The application becomes available through the Render service. -The production environment relies on: +Configuration management +------------------------ -- PostgreSQL for persistent data storage; -- Gunicorn to serve the Django application; -- WhiteNoise for static files handling. +Sensitive configuration values are provided through environment variables. -Monitoring ----------- +The following elements are configured externally: -Application monitoring is provided through Sentry, which collects runtime -errors and helps identify production issues. \ No newline at end of file +- Django secret key; +- database credentials; +- deployment settings. diff --git a/docs/source/index.rst b/docs/source/index.rst index 6821f22bdd..e872b70f8a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,15 +1,9 @@ -.. Orange County Lettings documentation master file, created by - sphinx-quickstart on Thu Jul 2 19:24:47 2026. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - Orange County Lettings documentation ==================================== Welcome to the technical documentation of the Orange County Lettings project. -This documentation explains the project's architecture, installation process, -development workflow, deployment pipeline and application components. +This documentation explains the project's architecture, installation process, integration and deployment pipeline and application components. Use the navigation menu below to browse each section. @@ -24,4 +18,5 @@ Use the navigation menu below to browse each section. database views_and_endpoints use_cases + pipeline deployment diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 1785e95e1a..93ca78b210 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -1,7 +1,7 @@ Installation ============ -This section explains how to install and configure the Orange County Lettings project in a local environment. +This section explains how to install and configure the Orange County Lettings project in a local environment or with Docker. It complements the README by providing a more detailed setup procedure and environment configuration. @@ -17,7 +17,6 @@ Ensure the following tools are installed: - Git - Python 3.10+ -- pip - Docker and Docker Compose (optional but recommended) Project cloning @@ -133,13 +132,5 @@ This is handled by the `start.sh` script and ensures the database schema is alwa Environment differences ----------------------- -- Local setup: SQLite (lightweight, development-friendly) +- Local setup: SQLite (lightweight, portability) - Docker setup: PostgreSQL (production-like) - -Summary -------- - -Two execution modes are available: - -- local environment for development and debugging -- Docker environment for production-like execution \ No newline at end of file diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 09578d1864..a221c2ef2e 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -6,7 +6,7 @@ Project overview Orange County Lettings is a Django-based web application that allows users to browse property listings and view detailed information about associated user profiles. -The project is designed with a modular architecture to ensure a clear separation of concerns between its different components (users, listings, main site, etc.). +The project is designed with a modular architecture to ensure a clear separation of concerns between its different components (profiles, lettings, main site, etc.). Development context ------------------- @@ -26,7 +26,7 @@ Technical objectives Beyond functional requirements, this project demonstrates the ability to manage a complete software development lifecycle: - Git and GitHub version control -- code quality enforcement with linting (flake8) +- code quality analysis with linting tools - unit testing with pytest - test coverage analysis - automated workflows using GitHub Actions @@ -39,8 +39,9 @@ High-level architecture The application consists of several core components: - a Django web application handling business logic and the user interface -- a relational database (SQLite in quickstart/testing, PostgreSQL in development production) +- a relational database (SQLite in quickstart/testing, PostgreSQL in development and production) - a CI/CD pipeline for automated testing and deployment -- a containerized infrastructure using Docker and Render +- a Docker-based deployment infrastructure hosted on Render -The following sections of this documentation provide more details on installation, usage, architecture, and deployment. \ No newline at end of file +The following sections of this documentation provide more details on +installation, architecture, application components, use cases and deployment. \ No newline at end of file diff --git a/docs/source/pipeline.rst b/docs/source/pipeline.rst new file mode 100644 index 0000000000..e773e93381 --- /dev/null +++ b/docs/source/pipeline.rst @@ -0,0 +1,126 @@ +Pipeline +======== + +The Orange County Lettings project uses a continuous integration and deployment +pipeline based on GitHub Actions. + +The pipeline automates code validation, Docker image creation, and production +deployment in order to ensure that only validated changes can reach the +production environment. + +Pipeline workflow +----------------- + +The global workflow follows these steps: + +#. A developer pushes changes or creates a Pull Request on GitHub. +#. The ``compile`` job validates the source code quality and runs automated tests. +#. When validation succeeds on the production branch, the ``containerize`` job + builds and publishes the Docker image. +#. The ``deploy`` job triggers the production deployment on Render. + +:: + + Developer + | + v + GitHub repository + | + v + GitHub Actions + | + +----------------+ + | compile | + |----------------| + | Dependencies | + | flake8 | + | pytest | + | coverage | + +----------------+ + | + v + (master branch only) + | + v + +----------------+ + | containerize | + |----------------| + | Docker build | + | Docker Hub | + +----------------+ + | + v + +----------------+ + | deploy | + |----------------| + | Render Hook | + +----------------+ + + +Compile job +----------- + +The ``compile`` job runs for every push and Pull Request. + +Its purpose is to validate that the application remains stable before any +deployment operation. + +The job performs: + +- installation of Python dependencies; +- static code analysis using ``flake8``; +- execution of automated tests using ``pytest``; +- measurement of test coverage. + +The application uses a minimum coverage threshold to prevent significant +regressions from being introduced. + +Containerize job +---------------- + +The ``containerize`` job runs only after a successful ``compile`` job and only +for changes merged into the production branch. + +It is responsible for creating and publishing the production Docker image. + +The job performs: + +- Docker image build; +- image tagging using the Git commit SHA; +- creation of a ``latest`` image tag; +- publication to Docker Hub. + +Using immutable SHA-based tags allows each production image to be uniquely +identified and traced back to a specific source revision. + +Deploy job +---------- + +The ``deploy`` job is executed after the Docker image has been successfully +published. + +It triggers a Render deployment through a Deploy Hook. + +This mechanism allows GitHub Actions to remotely start a new production +deployment without storing deployment credentials inside the repository. + +Monitoring and logging +---------------------- + +The application integrates Sentry to monitor runtime behavior and production +issues. + +The monitoring strategy relies on three levels of information: + +- **Logs**: general application events and informational messages used to + understand normal application activity; +- **Warnings**: abnormal situations that do not prevent the application from + running but may require attention; +- **Errors**: application failures requiring investigation, including server + errors such as HTTP 500 responses. + +HTTP 404 responses are also monitored to help identify invalid routes or +unexpected user navigation patterns. + +This monitoring approach provides visibility into application health after +deployment and helps detect issues that are not visible during automated tests. \ No newline at end of file diff --git a/docs/source/technologies.rst b/docs/source/technologies.rst index c0bfc274a2..1deb97e63e 100644 --- a/docs/source/technologies.rst +++ b/docs/source/technologies.rst @@ -1,7 +1,7 @@ Technologies ============ -The Orange County Lettings project is built on a modern Python web stack designed for scalability, maintainability, and deployment readiness. +The Orange County Lettings project is built on a Python web stack designed to improve maintainability, reproducibility, and deployment automation. Backend ------- @@ -19,7 +19,7 @@ Containerization ----------------- - Docker: container runtime for the application -- Docker Compose: orchestration of multi-service environments +- Docker Compose: configuration and orchestration of the application services CI/CD ----- @@ -39,7 +39,7 @@ Production stack - Gunicorn: WSGI HTTP server for running Django in production - WhiteNoise: static file serving without external storage -- Render: cloud platform used for deployment +- Render: cloud platform used for hosting and deployment Monitoring and configuration ----------------------------- diff --git a/docs/source/use_cases.rst b/docs/source/use_cases.rst index f3d6d6dcef..43a0bcac93 100644 --- a/docs/source/use_cases.rst +++ b/docs/source/use_cases.rst @@ -59,5 +59,5 @@ A typical contribution follows the project's development workflow: #. Create a feature branch. #. Implement the requested changes. #. Push the branch to GitHub. -#. Let the CI pipeline validate the contribution. +#. Trigger the CI pipeline to validate the contribution. #. Merge and deploy the application. \ No newline at end of file diff --git a/docs/source/views_and_endpoints.rst b/docs/source/views_and_endpoints.rst index cbeb83e507..d5b63eebab 100644 --- a/docs/source/views_and_endpoints.rst +++ b/docs/source/views_and_endpoints.rst @@ -1,8 +1,8 @@ Views and endpoints =================== -The application exposes HTTP endpoints through Django URL routing. -Each endpoint is associated with a view function responsible for processing +The application exposes web routes through Django URL routing. +Each route is associated with a view function responsible for processing requests and returning the appropriate response. Request handling @@ -11,7 +11,7 @@ Request handling Django processes incoming requests through the following workflow: 1. The URL dispatcher matches the requested path with a configured route. -2. The associated view retrieves or processes the required data. +2. The associated view retrieves or processes the required data using models. 3. The view renders a template with the provided context. 4. Django returns the generated HTTP response to the client. @@ -20,6 +20,7 @@ URL organization Each Django application defines its own URL configuration: +- ``oc_lettings_site``: global URL configuration; - ``lettings`` manages property listing pages; - ``profiles`` manages user profile pages. From 95a09246e6785d4d27488737329bc454f75b7542 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Tue, 7 Jul 2026 23:09:22 +0200 Subject: [PATCH 107/113] Final documentation reviewing, including minor correction and separation off deployment.rst into two separates files with clear purposes. --- docs/source/database.rst | 3 +-- docs/source/deployment.rst | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/database.rst b/docs/source/database.rst index 7c7b0553dc..483e8cb3a3 100644 --- a/docs/source/database.rst +++ b/docs/source/database.rst @@ -30,8 +30,7 @@ Main entities include: - ``Letting``: represents a property listing; - ``Address``: stores the location information associated with a letting; -- ``Profile``: extends Django's built-in authentication system by -associating additional information with each user. +- ``Profile``: extends Django's built-in authentication system by associating additional information with each user. The models are managed through Django migrations, which ensure that the database schema remains synchronized with the application code. diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst index 2b77374dd8..cfe2e4a5b5 100644 --- a/docs/source/deployment.rst +++ b/docs/source/deployment.rst @@ -24,7 +24,7 @@ When changes are merged into the production branch, the pipeline: #. Builds a new Docker image. #. Publishes the image to Docker Hub. -#.Triggers a deployment on Render using a Deploy Hook. +#. Triggers a deployment on Render using a Deploy Hook. #. Starts the updated production container. The Render Deploy Hook allows the CI/CD pipeline to remotely trigger a new From 62fbeafde437b7d5a87423075116dd72c58e7497 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 10 Jul 2026 13:23:23 +0200 Subject: [PATCH 108/113] Final documentation reviewing, including minor correction and separation off deployment.rst into two separates files with clear purposes. --- global_templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global_templates/index.html b/global_templates/index.html index 34cb562c7c..61d627ebd6 100644 --- a/global_templates/index.html +++ b/global_templates/index.html @@ -7,7 +7,7 @@
-

Welcome to Holiday Homes

+

Welcome to Holiday Homes my friend

From dfa1c60ecb834fe5a33b9046ed06022962f4fb3c Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Fri, 10 Jul 2026 15:36:21 +0200 Subject: [PATCH 109/113] Final documentation reviewing, including minor correction and separation off deployment.rst into two separates files with clear purposes. --- global_templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global_templates/index.html b/global_templates/index.html index 61d627ebd6..34cb562c7c 100644 --- a/global_templates/index.html +++ b/global_templates/index.html @@ -7,7 +7,7 @@
-

Welcome to Holiday Homes my friend

+

Welcome to Holiday Homes

From 6bc6583b3cd1fbb9a2e5ae135070476a76b88c2f Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sat, 11 Jul 2026 13:02:36 +0200 Subject: [PATCH 110/113] Final documentation reviewing, including minor correction and separation off deployment.rst into two separates files with clear purposes. --- global_templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global_templates/index.html b/global_templates/index.html index 34cb562c7c..61d627ebd6 100644 --- a/global_templates/index.html +++ b/global_templates/index.html @@ -7,7 +7,7 @@
-

Welcome to Holiday Homes

+

Welcome to Holiday Homes my friend

From 17f339711f8581a110962e31eb240ea1bcd5bc33 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 12 Jul 2026 00:16:19 +0200 Subject: [PATCH 111/113] Show off --- global_templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global_templates/index.html b/global_templates/index.html index 61d627ebd6..34cb562c7c 100644 --- a/global_templates/index.html +++ b/global_templates/index.html @@ -7,7 +7,7 @@
-

Welcome to Holiday Homes my friend

+

Welcome to Holiday Homes

From ac88223c0554842a788bf8f167fe65dd073dd4aa Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 12 Jul 2026 09:44:12 +0200 Subject: [PATCH 112/113] Show off --- global_templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global_templates/index.html b/global_templates/index.html index 34cb562c7c..61d627ebd6 100644 --- a/global_templates/index.html +++ b/global_templates/index.html @@ -7,7 +7,7 @@
-

Welcome to Holiday Homes

+

Welcome to Holiday Homes my friend

From af73fdd23d6def182e80598a77a97d117eef42e0 Mon Sep 17 00:00:00 2001 From: Jeremy Muller Date: Sun, 12 Jul 2026 10:10:22 +0200 Subject: [PATCH 113/113] Show off --- global_templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global_templates/index.html b/global_templates/index.html index 61d627ebd6..34cb562c7c 100644 --- a/global_templates/index.html +++ b/global_templates/index.html @@ -7,7 +7,7 @@
-

Welcome to Holiday Homes my friend

+

Welcome to Holiday Homes