diff --git a/blango/__pycache__/__init__.cpython-36.pyc b/blango/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000..90db682925 Binary files /dev/null and b/blango/__pycache__/__init__.cpython-36.pyc differ diff --git a/blango/__pycache__/settings.cpython-36.pyc b/blango/__pycache__/settings.cpython-36.pyc new file mode 100644 index 0000000000..6cc259b81c Binary files /dev/null and b/blango/__pycache__/settings.cpython-36.pyc differ diff --git a/blango/__pycache__/urls.cpython-36.pyc b/blango/__pycache__/urls.cpython-36.pyc new file mode 100644 index 0000000000..69daf2c87c Binary files /dev/null and b/blango/__pycache__/urls.cpython-36.pyc differ diff --git a/blango/__pycache__/wsgi.cpython-36.pyc b/blango/__pycache__/wsgi.cpython-36.pyc new file mode 100644 index 0000000000..d5af34a1ab Binary files /dev/null and b/blango/__pycache__/wsgi.cpython-36.pyc differ diff --git a/blango/settings.py b/blango/settings.py index f9209bef27..495fa886cf 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -1,125 +1,240 @@ -""" -Django settings for blango project. +import os +from pathlib import Path +from configurations import Configuration +from configurations import values +import dj_database_url +import argon2 + + +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", + +] +class Dev(Configuration): -Generated by 'django-admin startproject' using Django 3.2.7. + """ + Django settings for blango project. -For more information on this file, see -https://docs.djangoproject.com/en/3.2/topics/settings/ + Generated by 'django-admin startproject' using Django 3.2.7. -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.2/ref/settings/ -""" + For more information on this file, see + https://docs.djangoproject.com/en/3.2/topics/settings/ -from pathlib import Path + For the full list of settings and their values, see + https://docs.djangoproject.com/en/3.2/ref/settings/ + """ -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent + AUTH_USER_MODEL = "blango_auth.User" + + PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", + +] + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "filters": { + "require_debug_false": { + "()": "django.utils.log.RequireDebugFalse", + }, + }, + "formatters": { + "verbose": { + "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + "formatter": "verbose", + }, + "mail_admins": { + "level": "ERROR", + "class": "django.utils.log.AdminEmailHandler", + "filters": ["require_debug_false"], + }, + }, + "loggers": { + "django.request": { + "handlers": ["mail_admins"], + "level": "ERROR", + "propagate": True, + }, + }, + "root": { + "handlers": ["console"], + "level": "DEBUG", + }, +} -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-+sn%dpa!086+g+%44z9*^j^q-u4n!j(#wl)x9a%_1op@zz2+1-' -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True + TIME_ZONE = values.Value("UTC") + + + # Build paths inside the project like this: BASE_DIR / 'subdir'. + BASE_DIR = Path(__file__).resolve().parent.parent + + + # Quick-start development settings - unsuitable for production + # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + + # SECURITY WARNING: keep the secret key used in production secret! + SECRET_KEY = 'django-insecure-+sn%dpa!086+g+%44z9*^j^q-u4n!j(#wl)x9a%_1op@zz2+1-' + + # SECURITY WARNING: don't run with debug turned on in production! + DEBUG = True + + ALLOWED_HOSTS = ['*'] + X_FRAME_OPTIONS = 'ALLOW-FROM ' + os.environ.get('CODIO_HOSTNAME') + '-8000.codio.io' + CSRF_COOKIE_SAMESITE = None + CSRF_TRUSTED_ORIGINS = ['https://' + os.environ.get('CODIO_HOSTNAME') + '-8000.codio.io'] + CSRF_COOKIE_SECURE = True + SESSION_COOKIE_SECURE = True + CSRF_COOKIE_SAMESITE = 'None' + SESSION_COOKIE_SAMESITE = 'None' + + + # Application definition + + INSTALLED_APPS = [ + "blango_auth", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.sites", + "django.contrib.staticfiles", + "blog", + "crispy_forms", + "crispy_bootstrap5", + "debug_toolbar", + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.google" + ] + MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', + #'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + #'django.middleware.clickjacking.XFrameOptionsMiddleware', + ] + + ROOT_URLCONF = 'blango.urls' + + TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [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', + ], + }, + }, + ] -ALLOWED_HOSTS = [] + WSGI_APPLICATION = 'blango.wsgi.application' -# Application definition + # Database + # https://docs.djangoproject.com/en/3.2/ref/settings/#databases -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', -] + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } -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', -] + # Password validation + # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators -ROOT_URLCONF = 'blango.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], + AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, - }, -] + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, + ] -WSGI_APPLICATION = 'blango.wsgi.application' + # Internationalization + # https://docs.djangoproject.com/en/3.2/topics/i18n/ -# Database -# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + LANGUAGE_CODE = 'en-us' -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} + + USE_I18N = True -# Password validation -# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] + USE_L10N = True + USE_TZ = True -# Internationalization -# https://docs.djangoproject.com/en/3.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' + # Static files (CSS, JavaScript, Images) + # https://docs.djangoproject.com/en/3.2/howto/static-files/ -TIME_ZONE = 'UTC' + STATIC_URL = '/static/' -USE_I18N = True + # Default primary key field type + # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field -USE_L10N = True + DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -USE_TZ = True + CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" + CRISPY_TEMPLATE_PACK = "bootstrap5" + INTERNAL_IPS = ["192.168.10.226"] + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + ACCOUNT_ACTIVATION_DAYS = 7 + SITE_ID = 1 + ACCOUNT_USER_MODEL_USERNAME_FIELD = None + ACCOUNT_EMAIL_REQUIRED = True + ACCOUNT_USERNAME_REQUIRED = False + ACCOUNT_AUTHENTICATION_METHOD = "email" -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +class Prod(Dev): + BASE_DIR = Path(__file__).resolve().parent.parent + DEBUG = True + DEBUG = values.BooleanValue(True) + SECRET_KEY = values.SecretValue() + ALLOWED_HOSTS = values.ListValue(["localhost", "0.0.0.0", ".codio.io"]) -STATIC_URL = '/static/' - -# Default primary key field type -# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + DATABASES = values.DatabaseURLValue(f"sqlite:///{BASE_DIR}/db.sqlite3") + DATABASES = { + "default": dj_database_url.config(default=f"sqlite:///{BASE_DIR}/db.sqlite3"), + "alternative": dj_database_url.config( + "ALTERNATIVE_DATABASE_URL", + default=f"sqlite:///{BASE_DIR}/alternative_db.sqlite3", + ), + } -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/blango/urls.py b/blango/urls.py index cde05802f9..7410f7e514 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -13,9 +13,34 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +import debug_toolbar +from django.conf import settings from django.contrib import admin -from django.urls import path +from django.urls import path, include +from django_registration.backends.activation.views import RegistrationView +from blango_auth.forms import BlangoRegistrationForm + +import blog.views +import blango_auth.views + + urlpatterns = [ path('admin/', admin.site.urls), + path("ip/", blog.views.get_ip), + path("", blog.views.index), + path("post//", blog.views.post_detail, name="blog-post-detail"), + path("accounts/", include("django.contrib.auth.urls")), + path("accounts/profile/", blango_auth.views.profile, name="profile"), + path( + "accounts/register/", + RegistrationView.as_view(form_class=BlangoRegistrationForm), + name="django_registration_register",), + path("accounts/", include("django_registration.backends.activation.urls")), + path("accounts/", include("allauth.urls")), ] + +if settings.DEBUG: + urlpatterns += [ + path("__debug__/", include(debug_toolbar.urls)), + ] diff --git a/blango/wsgi.py b/blango/wsgi.py index 83565cf12c..50fe8ff186 100644 --- a/blango/wsgi.py +++ b/blango/wsgi.py @@ -9,8 +9,11 @@ import os -from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blango.settings') + + +os.environ.setdefault("DJANGO_CONFIGURATION", "Prod") + +from configurations.wsgi import get_wsgi_application application = get_wsgi_application() diff --git a/blango_auth/__init__.py b/blango_auth/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blango_auth/__pycache__/__init__.cpython-36.pyc b/blango_auth/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000..405396ee43 Binary files /dev/null and b/blango_auth/__pycache__/__init__.cpython-36.pyc differ diff --git a/blango_auth/__pycache__/admin.cpython-36.pyc b/blango_auth/__pycache__/admin.cpython-36.pyc new file mode 100644 index 0000000000..53df26584d Binary files /dev/null and b/blango_auth/__pycache__/admin.cpython-36.pyc differ diff --git a/blango_auth/__pycache__/apps.cpython-36.pyc b/blango_auth/__pycache__/apps.cpython-36.pyc new file mode 100644 index 0000000000..27896673f3 Binary files /dev/null and b/blango_auth/__pycache__/apps.cpython-36.pyc differ diff --git a/blango_auth/__pycache__/forms.cpython-36.pyc b/blango_auth/__pycache__/forms.cpython-36.pyc new file mode 100644 index 0000000000..3521052fa6 Binary files /dev/null and b/blango_auth/__pycache__/forms.cpython-36.pyc differ diff --git a/blango_auth/__pycache__/models.cpython-36.pyc b/blango_auth/__pycache__/models.cpython-36.pyc new file mode 100644 index 0000000000..c7a906e5e4 Binary files /dev/null and b/blango_auth/__pycache__/models.cpython-36.pyc differ diff --git a/blango_auth/__pycache__/views.cpython-36.pyc b/blango_auth/__pycache__/views.cpython-36.pyc new file mode 100644 index 0000000000..c56b0a7e03 Binary files /dev/null and b/blango_auth/__pycache__/views.cpython-36.pyc differ diff --git a/blango_auth/admin.py b/blango_auth/admin.py new file mode 100644 index 0000000000..c579f2d50f --- /dev/null +++ b/blango_auth/admin.py @@ -0,0 +1,47 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from blango_auth.models import User +from django.utils.translation import gettext_lazy as _ +# Register your models here. + + +class BlangoUserAdmin(UserAdmin): + fieldsets = ( + (None, {"fields": ("email", "password")}), + (_("Personal info"), {"fields": ("first_name", "last_name")}), + ( + _("Permissions"), + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + "groups", + "user_permissions", + ) + }, + ), + (_("Important dates"), {"fields": ("last_login", "date_joined")}), + ) + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ("email", "password1", "password2"), + }, + ), + ) + list_display = ("email", "first_name", "last_name", "is_staff") + search_fields = ("email", "first_name", "last_name") + ordering = ("email",) + + + +admin.site.register(User, BlangoUserAdmin) + + + + + + diff --git a/blango_auth/apps.py b/blango_auth/apps.py new file mode 100644 index 0000000000..3619a45e56 --- /dev/null +++ b/blango_auth/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BlangoAuthConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'blango_auth' diff --git a/blango_auth/forms.py b/blango_auth/forms.py new file mode 100644 index 0000000000..bc7abbac83 --- /dev/null +++ b/blango_auth/forms.py @@ -0,0 +1,15 @@ +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Submit +from django_registration.forms import RegistrationForm + +from blango_auth.models import User + + +class BlangoRegistrationForm(RegistrationForm): + class Meta(RegistrationForm.Meta): + model = User + + def __init__(self, *args, **kwargs): + super(BlangoRegistrationForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.add_input(Submit("submit", "Register")) \ No newline at end of file diff --git a/blango_auth/migrations/0001_initial.py b/blango_auth/migrations/0001_initial.py new file mode 100644 index 0000000000..10a59d6a53 --- /dev/null +++ b/blango_auth/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.6 on 2024-10-05 00:51 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/blango_auth/migrations/0002_auto_20241005_0122.py b/blango_auth/migrations/0002_auto_20241005_0122.py new file mode 100644 index 0000000000..5622812d14 --- /dev/null +++ b/blango_auth/migrations/0002_auto_20241005_0122.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.6 on 2024-10-05 01:22 + +import blango_auth.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blango_auth', '0001_initial'), + ] + + operations = [ + migrations.AlterModelManagers( + name='user', + managers=[ + ('objects', blango_auth.models.BlangoUserManager()), + ], + ), + migrations.RemoveField( + model_name='user', + name='username', + ), + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(max_length=254, unique=True, verbose_name='email address'), + ), + ] diff --git a/blango_auth/migrations/__init__.py b/blango_auth/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blango_auth/migrations/__pycache__/0001_initial.cpython-36.pyc b/blango_auth/migrations/__pycache__/0001_initial.cpython-36.pyc new file mode 100644 index 0000000000..6e86b36503 Binary files /dev/null and b/blango_auth/migrations/__pycache__/0001_initial.cpython-36.pyc differ diff --git a/blango_auth/migrations/__pycache__/0002_auto_20241005_0122.cpython-36.pyc b/blango_auth/migrations/__pycache__/0002_auto_20241005_0122.cpython-36.pyc new file mode 100644 index 0000000000..bddfdedb41 Binary files /dev/null and b/blango_auth/migrations/__pycache__/0002_auto_20241005_0122.cpython-36.pyc differ diff --git a/blango_auth/migrations/__pycache__/__init__.cpython-36.pyc b/blango_auth/migrations/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000..d3b1f1ffee Binary files /dev/null and b/blango_auth/migrations/__pycache__/__init__.cpython-36.pyc differ diff --git a/blango_auth/models.py b/blango_auth/models.py new file mode 100644 index 0000000000..6000c587c0 --- /dev/null +++ b/blango_auth/models.py @@ -0,0 +1,45 @@ +from django.db import models +from django.contrib.auth.models import AbstractUser, UserManager +from django.utils.translation import gettext_lazy as _ +# Create your models here. +class BlangoUserManager(UserManager): + def _create_user(self, email, password, **extra_fields): + if not email: + raise ValueError("Email must be set") + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_user(self, email, password=None, **extra_fields): + extra_fields.setdefault("is_staff", False) + extra_fields.setdefault("is_superuser", False) + return self._create_user(email, password, **extra_fields) + + def create_superuser(self, email, password, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") + + return self._create_user(email, password, **extra_fields) + + +class User(AbstractUser): + username = None + email = models.EmailField( + _("email address"), + unique=True, + ) + + objects = BlangoUserManager() + + USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + def __str__(self): + return self.email \ No newline at end of file diff --git a/blango_auth/templates/blango_auth/profile.html b/blango_auth/templates/blango_auth/profile.html new file mode 100644 index 0000000000..e86c690ee5 --- /dev/null +++ b/blango_auth/templates/blango_auth/profile.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% load blog_extras %} +{% block title %}Blango Profile{% endblock %} +{% block content %} +{% row %} + {% col %} +

Logged in as {{ request.user }}.

+

Log Out

+ {% endcol %} +{% endrow %} +{% endblock content %} \ No newline at end of file diff --git a/blango_auth/templates/django_registration/activation_complete.html b/blango_auth/templates/django_registration/activation_complete.html new file mode 100644 index 0000000000..1453e28091 --- /dev/null +++ b/blango_auth/templates/django_registration/activation_complete.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load crispy_forms_tags blog_extras %} +{% block title %}Activation Complete{% endblock %} +{% block content %} +{% row "justify-content-center" %} + {% col "col-md-6" %} +

Activation Complete

+

Your account is now activated! You can now log in and use Blango.

+

Log In

+ {% endcol %} +{% endrow %} +{% endblock %} \ No newline at end of file diff --git a/blango_auth/templates/django_registration/activation_email_body.txt b/blango_auth/templates/django_registration/activation_email_body.txt new file mode 100644 index 0000000000..5faf606264 --- /dev/null +++ b/blango_auth/templates/django_registration/activation_email_body.txt @@ -0,0 +1,10 @@ +Hi, + +You registered for Blango, but you need to activate your account within {{ expiration_days }} days. + +To do that, please visit this page: + +{{ scheme }}://{{ request.get_host }}{% url "django_registration_activate" activation_key %} + +Thanks, +The Blango Team \ No newline at end of file diff --git a/blango_auth/templates/django_registration/activation_email_subject.txt b/blango_auth/templates/django_registration/activation_email_subject.txt new file mode 100644 index 0000000000..67646d771b --- /dev/null +++ b/blango_auth/templates/django_registration/activation_email_subject.txt @@ -0,0 +1 @@ +Activate your Blango account! You have {{ expiration_days }} days! \ No newline at end of file diff --git a/blango_auth/templates/django_registration/activation_failed.html b/blango_auth/templates/django_registration/activation_failed.html new file mode 100644 index 0000000000..5631e8a64b --- /dev/null +++ b/blango_auth/templates/django_registration/activation_failed.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load crispy_forms_tags blog_extras %} +{% block title %}Activation Failed{% endblock %} +{% block content %} +{% row "justify-content-center" %} + {% col "col-md-6" %} +

Activation Failed

+

Sorry, we couldn't activate your account.

+

{{ activation_error.message }}

+ {% endcol %} +{% endrow %} +{% endblock %} \ No newline at end of file diff --git a/blango_auth/templates/django_registration/registration_closed.html b/blango_auth/templates/django_registration/registration_closed.html new file mode 100644 index 0000000000..b15b741712 --- /dev/null +++ b/blango_auth/templates/django_registration/registration_closed.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% load crispy_forms_tags blog_extras %} +{% block title %}Registration Closed{% endblock %} +{% block content %} +{% row "justify-content-center" %} + {% col "col-md-6" %} +

Registration is currently closed

+ {% endcol %} +{% endrow %} +{% endblock %} \ No newline at end of file diff --git a/blango_auth/templates/django_registration/registration_complete.html b/blango_auth/templates/django_registration/registration_complete.html new file mode 100644 index 0000000000..5a37d3ce5f --- /dev/null +++ b/blango_auth/templates/django_registration/registration_complete.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% load crispy_forms_tags blog_extras %} +{% block title %}Registration Successful{% endblock %} +{% block content %} +{% row "justify-content-center" %} + {% col "col-md-6" %} +

Your registration was successful

+

Check your email to validate your account.

+ {% endcol %} +{% endrow %} +{% endblock %} \ No newline at end of file diff --git a/blango_auth/templates/django_registration/registration_form.html b/blango_auth/templates/django_registration/registration_form.html new file mode 100644 index 0000000000..c7f501a423 --- /dev/null +++ b/blango_auth/templates/django_registration/registration_form.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% load crispy_forms_tags blog_extras %} +{% block title %}Register for Blango{% endblock %} +{% block content %} +{% row "justify-content-center" %} + {% col "col-md-6" %} +

Register for Blango

+ {% crispy form %} + {% endcol %} +{% endrow %} +{% endblock %} \ No newline at end of file diff --git a/blango_auth/templates/registration/login.html b/blango_auth/templates/registration/login.html new file mode 100644 index 0000000000..f339161b3b --- /dev/null +++ b/blango_auth/templates/registration/login.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% load crispy_forms_tags blog_extras %} +{% block title %}Log In to Blango{% endblock %} +{% block content %} +{% row "justify-content-center" %} + {% col "col-md-6" %} + {% if next %} + {% if user.is_authenticated %} +

Your account doesn't have access to this page. To proceed, + please login with an account that has access.

+ {% else %} +

Please login to see this page.

+ {% endif %} + {% endif %} + {% endcol %} +{% endrow %} + +{% row "justify-content-center" %} + {% col "col-md-6" %} +
+ {% csrf_token %} + {{ form|crispy }} + + +
+ +

Lost password?

+

+ Log in with Google +

+ {% endcol %} +{% endrow %} +{% endblock %} \ No newline at end of file diff --git a/blango_auth/tests.py b/blango_auth/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/blango_auth/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/blango_auth/views.py b/blango_auth/views.py new file mode 100644 index 0000000000..db63e0eee2 --- /dev/null +++ b/blango_auth/views.py @@ -0,0 +1,10 @@ +from django.shortcuts import render + +# Create your views here. +from django.contrib.auth.decorators import login_required +from django.shortcuts import render + + +@login_required +def profile(request): + return render(request, "blango_auth/profile.html") \ No newline at end of file diff --git a/blog/__init__.py b/blog/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blog/__pycache__/__init__.cpython-36.pyc b/blog/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000..d2c2acd84e Binary files /dev/null and b/blog/__pycache__/__init__.cpython-36.pyc differ diff --git a/blog/__pycache__/admin.cpython-36.pyc b/blog/__pycache__/admin.cpython-36.pyc new file mode 100644 index 0000000000..f212fda0f3 Binary files /dev/null and b/blog/__pycache__/admin.cpython-36.pyc differ diff --git a/blog/__pycache__/apps.cpython-36.pyc b/blog/__pycache__/apps.cpython-36.pyc new file mode 100644 index 0000000000..73aa7407f9 Binary files /dev/null and b/blog/__pycache__/apps.cpython-36.pyc differ diff --git a/blog/__pycache__/forms.cpython-36.pyc b/blog/__pycache__/forms.cpython-36.pyc new file mode 100644 index 0000000000..dd3d345193 Binary files /dev/null and b/blog/__pycache__/forms.cpython-36.pyc differ diff --git a/blog/__pycache__/models.cpython-36.pyc b/blog/__pycache__/models.cpython-36.pyc new file mode 100644 index 0000000000..c7ceb23efe Binary files /dev/null and b/blog/__pycache__/models.cpython-36.pyc differ diff --git a/blog/__pycache__/views.cpython-36.pyc b/blog/__pycache__/views.cpython-36.pyc new file mode 100644 index 0000000000..eaa6444464 Binary files /dev/null and b/blog/__pycache__/views.cpython-36.pyc differ diff --git a/blog/admin.py b/blog/admin.py new file mode 100644 index 0000000000..ffcebcb787 --- /dev/null +++ b/blog/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from blog.models import Tag, Post, Comment, AuthorProfile + +class PostAdmin(admin.ModelAdmin): + prepopulated_fields = {"slug": ["title"]} + +# Register your models here. +admin.site.register(Tag) +admin.site.register(Post, PostAdmin) +admin.site.register(Comment) +admin.site.register(AuthorProfile) \ No newline at end of file diff --git a/blog/apps.py b/blog/apps.py new file mode 100644 index 0000000000..94788a5eac --- /dev/null +++ b/blog/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'blog' diff --git a/blog/forms.py b/blog/forms.py new file mode 100644 index 0000000000..1cc247f97e --- /dev/null +++ b/blog/forms.py @@ -0,0 +1,16 @@ +from django import forms +from blog.models import Comment +from crispy_forms.layout import Submit +from crispy_forms.helper import FormHelper + + +class CommentForm(forms.ModelForm): + class Meta: + model = Comment + fields = ['content'] + + + def __init__(self, *args, **kwargs): + super(CommentForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.add_input(Submit('submit', 'Submit')) \ No newline at end of file diff --git a/blog/migrations/0001_initial.py b/blog/migrations/0001_initial.py new file mode 100644 index 0000000000..c63b9dbccc --- /dev/null +++ b/blog/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.5 on 2024-09-05 08:06 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.TextField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('modified_at', models.DateTimeField(auto_now=True)), + ('published_at', models.DateTimeField(blank=True, null=True)), + ('title', models.TextField(max_length=100)), + ('slug', models.SlugField()), + ('summary', models.TextField(max_length=500)), + ('content', models.TextField()), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ('tags', models.ManyToManyField(related_name='posts', to='blog.Tag')), + ], + ), + ] diff --git a/blog/migrations/0002_comment.py b/blog/migrations/0002_comment.py new file mode 100644 index 0000000000..a35f41892b --- /dev/null +++ b/blog/migrations/0002_comment.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.5 on 2024-09-07 08:42 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), + ('blog', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField()), + ('object_id', models.PositiveIntegerField()), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/blog/migrations/0003_auto_20240907_1018.py b/blog/migrations/0003_auto_20240907_1018.py new file mode 100644 index 0000000000..0c4160c978 --- /dev/null +++ b/blog/migrations/0003_auto_20240907_1018.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.5 on 2024-09-07 10:18 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0002_comment'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='comment', + name='modified_at', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/blog/migrations/0004_alter_post_published_at.py b/blog/migrations/0004_alter_post_published_at.py new file mode 100644 index 0000000000..fa6ea22644 --- /dev/null +++ b/blog/migrations/0004_alter_post_published_at.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.25 on 2024-09-24 17:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0003_auto_20240907_1018'), + ] + + operations = [ + migrations.AlterField( + model_name='post', + name='published_at', + field=models.DateTimeField(blank=True, db_index=True, null=True), + ), + ] diff --git a/blog/migrations/0005_auto_20240924_1811.py b/blog/migrations/0005_auto_20240924_1811.py new file mode 100644 index 0000000000..5eb6185aab --- /dev/null +++ b/blog/migrations/0005_auto_20240924_1811.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.25 on 2024-09-24 18:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0004_alter_post_published_at'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, db_index=True), + ), + migrations.AlterField( + model_name='comment', + name='object_id', + field=models.PositiveIntegerField(db_index=True), + ), + ] diff --git a/blog/migrations/0006_authorprofile.py b/blog/migrations/0006_authorprofile.py new file mode 100644 index 0000000000..6cd2716be3 --- /dev/null +++ b/blog/migrations/0006_authorprofile.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.6 on 2024-10-05 00:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('blog', '0005_auto_20240924_1811'), + ] + + operations = [ + migrations.CreateModel( + name='AuthorProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('bio', models.TextField()), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/blog/migrations/__init__.py b/blog/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blog/migrations/__pycache__/0001_initial.cpython-36.pyc b/blog/migrations/__pycache__/0001_initial.cpython-36.pyc new file mode 100644 index 0000000000..b4728902ca Binary files /dev/null and b/blog/migrations/__pycache__/0001_initial.cpython-36.pyc differ diff --git a/blog/migrations/__pycache__/0002_comment.cpython-36.pyc b/blog/migrations/__pycache__/0002_comment.cpython-36.pyc new file mode 100644 index 0000000000..d5daf6871d Binary files /dev/null and b/blog/migrations/__pycache__/0002_comment.cpython-36.pyc differ diff --git a/blog/migrations/__pycache__/0003_auto_20240907_1018.cpython-36.pyc b/blog/migrations/__pycache__/0003_auto_20240907_1018.cpython-36.pyc new file mode 100644 index 0000000000..5dba827416 Binary files /dev/null and b/blog/migrations/__pycache__/0003_auto_20240907_1018.cpython-36.pyc differ diff --git a/blog/migrations/__pycache__/0004_alter_post_published_at.cpython-36.pyc b/blog/migrations/__pycache__/0004_alter_post_published_at.cpython-36.pyc new file mode 100644 index 0000000000..3ca8b76093 Binary files /dev/null and b/blog/migrations/__pycache__/0004_alter_post_published_at.cpython-36.pyc differ diff --git a/blog/migrations/__pycache__/0005_auto_20240924_1811.cpython-36.pyc b/blog/migrations/__pycache__/0005_auto_20240924_1811.cpython-36.pyc new file mode 100644 index 0000000000..9e398805ad Binary files /dev/null and b/blog/migrations/__pycache__/0005_auto_20240924_1811.cpython-36.pyc differ diff --git a/blog/migrations/__pycache__/0006_authorprofile.cpython-36.pyc b/blog/migrations/__pycache__/0006_authorprofile.cpython-36.pyc new file mode 100644 index 0000000000..55f44bc591 Binary files /dev/null and b/blog/migrations/__pycache__/0006_authorprofile.cpython-36.pyc differ diff --git a/blog/migrations/__pycache__/__init__.cpython-36.pyc b/blog/migrations/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000..c648fc2b70 Binary files /dev/null and b/blog/migrations/__pycache__/__init__.cpython-36.pyc differ diff --git a/blog/models.py b/blog/models.py new file mode 100644 index 0000000000..bdfb5acadc --- /dev/null +++ b/blog/models.py @@ -0,0 +1,46 @@ +from django.db import models +from django.conf import settings +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes.fields import GenericRelation + +# Create your models here. +class Tag(models.Model): + value = models.TextField(max_length=100) + + def __str__(self): + return self.value + +class Comment(models.Model): + creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + content = models.TextField() + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField(db_index = True) + content_object = GenericForeignKey("content_type", "object_id") + created_at = models.DateTimeField(auto_now_add=True,db_index = True) + modified_at = models.DateTimeField(auto_now=True) + +class Post(models.Model): + author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) + created_at = models.DateTimeField(auto_now_add=True) + modified_at = models.DateTimeField(auto_now=True) + published_at = models.DateTimeField(blank=True, null=True,db_index =True ) + title = models.TextField(max_length=100) + slug = models.SlugField() + summary = models.TextField(max_length=500) + content = models.TextField() + tags = models.ManyToManyField(Tag, related_name="posts") + comments = GenericRelation(Comment) + + def __str__(self): + return self.title + + +class AuthorProfile(models.Model): + user = models.OneToOneField( + settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile" + ) + bio = models.TextField() + + def __str__(self): + return f"{self.__class__.__name__} object for {self.user}" \ No newline at end of file diff --git a/blog/templatetags/__init__.py b/blog/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blog/templatetags/__pycache__/__init__.cpython-36.pyc b/blog/templatetags/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000..5ecea1595e Binary files /dev/null and b/blog/templatetags/__pycache__/__init__.cpython-36.pyc differ diff --git a/blog/templatetags/__pycache__/blog_extras.cpython-36.pyc b/blog/templatetags/__pycache__/blog_extras.cpython-36.pyc new file mode 100644 index 0000000000..46b4fb6317 Binary files /dev/null and b/blog/templatetags/__pycache__/blog_extras.cpython-36.pyc differ diff --git a/blog/templatetags/blog_extras.py b/blog/templatetags/blog_extras.py new file mode 100644 index 0000000000..b05c9dae3d --- /dev/null +++ b/blog/templatetags/blog_extras.py @@ -0,0 +1,65 @@ +from django import template +from django.contrib.auth import get_user_model +from django.utils.html import escape,format_html +from django.utils.safestring import mark_safe +from blog.models import Post +import logging +logger = logging.getLogger(__name__) + + + + +register = template.Library() +user_model = get_user_model() + +@register.simple_tag +def row(extra_classes=""): + return format_html('
',extra_classes) + + +@register.simple_tag +def endrow(): + return format_html("
") + + +@register.simple_tag +def col(extra_classes=""): + return format_html('
', extra_classes) + + +@register.simple_tag +def endcol(): + return format_html("
") + + +@register.inclusion_tag("blog/post-list.html") +def recent_posts(post): + posts = Post.objects.exclude(pk=post.pk)[:5] + logger.debug("Loaded %d recent posts for post %d", len(posts), post.pk) + return {"title": "Recent Posts", "posts": posts} + + +@register.simple_tag(takes_context=True) +def author_details_tag(context): + request = context["request"] + current_user = request.user + post = context["post"] + author = post.author + + if author == current_user: + return format_html("me") + + if author.first_name and author.last_name: + name = f"{author.first_name} {author.last_name}" + else: + name = f"{author.username}" + + if author.email: + prefix = format_html('', author.email) + suffix = format_html("") + else: + prefix = "" + suffix = "" + + return format_html("{}{}{}", prefix, name, suffix) + diff --git a/blog/tests.py b/blog/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/blog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/blog/views.py b/blog/views.py new file mode 100644 index 0000000000..34d1c6f714 --- /dev/null +++ b/blog/views.py @@ -0,0 +1,49 @@ +from django.shortcuts import render,get_object_or_404,redirect +from django.utils import timezone +from blog.models import Post +from blog.forms import CommentForm + +from django.views.decorators.cache import cache_page +from django.views.decorators.vary import vary_on_cookie + +import logging +logger = logging.getLogger(__name__) + + +# Create your views here. +@cache_page(300) +@vary_on_cookie +def index(request): + + posts = Post.objects.filter(published_at__lte=timezone.now()).select_related("author") + logger.debug("Got %d posts", len(posts)) + return render(request, "blog/index.html", {"posts": posts}) + + +def post_detail(request,slug): + + post = get_object_or_404(Post,slug=slug) + logger.info( + "Created comment on Post %d for user %s", post.pk, request.user +) + if request.user.is_active: + if request.method == "POST": + comment_form = CommentForm(request.POST) + + if comment_form.is_valid(): + comment = comment_form.save(commit=False) + comment.content_object = post + comment.creator = request.user + comment.save() + return redirect(request.path_info) + else: + comment_form = CommentForm() + else: + comment_form = None + + return render(request,"blog/post-detail.html",{'post':post,"comment_form": comment_form}) + + +def get_ip(request): + from django.http import HttpResponse + return HttpResponse(request.META['REMOTE_ADDR']) \ No newline at end of file diff --git a/data.json b/data.json new file mode 100644 index 0000000000..30229f6d52 --- /dev/null +++ b/data.json @@ -0,0 +1 @@ +[{"model": "auth.user", "pk": 1, "fields": {"password": "argon2$argon2id$v=19$m=102400,t=2,p=8$SzUyWk1nV0hNblVFSm9Sc29wcmhKUg$yqkcmH4ZOFp4xCOLrG4KBA", "last_login": null, "is_superuser": true, "username": "codio", "first_name": "", "last_name": "", "email": "example@gmail.com", "is_staff": true, "is_active": true, "date_joined": "2024-10-05T00:23:29.550Z", "groups": [], "user_permissions": []}}] \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000..c686950c0d Binary files /dev/null and b/db.sqlite3 differ diff --git a/manage.py b/manage.py index c66b327f71..b509d4d7ac 100644 --- a/manage.py +++ b/manage.py @@ -7,14 +7,15 @@ def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blango.settings') + os.environ.setdefault("DJANGO_CONFIGURATION", "Dev") try: - from django.core.management import execute_from_command_line + from configurations.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" - ) from exc + ) #from exc execute_from_command_line(sys.argv) diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000000..3d33ebc41e --- /dev/null +++ b/templates/base.html @@ -0,0 +1,29 @@ + + + + + + + <title>{% block title %}Welcome to Blango{% endblock %} + + + + {% block content %} + + {% endblock %} + + + \ No newline at end of file diff --git a/templates/blog/index.html b/templates/blog/index.html new file mode 100644 index 0000000000..ec9337524e --- /dev/null +++ b/templates/blog/index.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load blog_extras %} +{% block content %} +

Blog Posts

+ {% for post in posts %} + {% row "border-bottom" %} +
+

{{ post.title }}

+ {% include "blog/post-byline.html" %} +

{{ post.summary }}

+

+ ({{ post.content|wordcount }} words) + Read More +

+
+ {% endrow %} + {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/templates/blog/post-byline.html b/templates/blog/post-byline.html new file mode 100644 index 0000000000..3b00a83c48 --- /dev/null +++ b/templates/blog/post-byline.html @@ -0,0 +1,2 @@ +{% load blog_extras %} +By {{ author_details_tag}} on {{ post.published_at|date:"M, d Y" }} \ No newline at end of file diff --git a/templates/blog/post-comments.html b/templates/blog/post-comments.html new file mode 100644 index 0000000000..4e57d97fca --- /dev/null +++ b/templates/blog/post-comments.html @@ -0,0 +1,34 @@ +{% load blog_extras crispy_forms_tags %} +

Comments

+{% for comment in post.comments.all %} +{% row "border-top pt-2" %} + {% col %} +
Posted by {{ comment.creator }} at {{ comment.created_at|date:"M, d Y h:i" }}
+ {% endcol %} +{% endrow %} +{% row "border-bottom" %} + {% col %} +

{{ comment.content }}

+ {% endcol %} +{% endrow %} +{% empty %} + {% row "border-top border-bottom" %} + {% col %} +

No comments.

+ {% endcol %} + {% endrow %} +{% endfor %} +{% if request.user.is_active %} +{% row "mt-4" %} + {% col %} +

Add Comment

+
+ {% csrf_token %} + {{ comment_form|crispy }} +

+ +

+
+ {% endcol %} +{% endrow %} +{% endif %} \ No newline at end of file diff --git a/templates/blog/post-detail.html b/templates/blog/post-detail.html new file mode 100644 index 0000000000..50bd49b676 --- /dev/null +++ b/templates/blog/post-detail.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% load blog_extras cache %} + +{% block content %} +

{{ post.title }}

+{% row %} +
+ {% include "blog/post-byline.html" %} +
+{% endrow %} + +{% if post.author.profile %} + {% row %} + {% col %} +

About the author

+

{{ post.author.profile.bio }}

+ {% endcol %} + {% endrow %} +{% endif %} +{% row %} +
+ {{ post.content|safe }} +
+{% endrow %} + +{% include "blog/post-comments.html" %} +{% row %} + {% col %} + {% cache 3600 recent_posts post%} + {% recent_posts post %} + {% endcache %} + {% endcol %} +{% endrow %} + +{% endblock %} \ No newline at end of file diff --git a/templates/blog/post-list.html b/templates/blog/post-list.html new file mode 100644 index 0000000000..35e5572af5 --- /dev/null +++ b/templates/blog/post-list.html @@ -0,0 +1,6 @@ +

{{ title }}

+ \ No newline at end of file