diff --git a/blango/__pycache__/__init__.cpython-311.pyc b/blango/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000..271e86f76c Binary files /dev/null and b/blango/__pycache__/__init__.cpython-311.pyc differ diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000000..b51fed2619 Binary files /dev/null and b/blango/__pycache__/settings.cpython-311.pyc differ diff --git a/blango/__pycache__/urls.cpython-311.pyc b/blango/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000..96a223ba30 Binary files /dev/null and b/blango/__pycache__/urls.cpython-311.pyc differ diff --git a/blango/__pycache__/wsgi.cpython-311.pyc b/blango/__pycache__/wsgi.cpython-311.pyc new file mode 100644 index 0000000000..52e81caee0 Binary files /dev/null and b/blango/__pycache__/wsgi.cpython-311.pyc differ diff --git a/blango/data.json b/blango/data.json new file mode 100644 index 0000000000..bcb6ea11ef --- /dev/null +++ b/blango/data.json @@ -0,0 +1 @@ +{"model": "blog.comment", "pk": 1, "fields": {"creator": 1, "content": "What a great post!", "content_type": 7, "object_id": 1, "created_at": "2024-04-27T08:05:47.496Z", "modified_at": "2024-04-27T08:05:47.496Z"}}, {"model": "blog.comment", "pk": 2, "fields": {"creator": 1, "content": "This is a comment caputured on the web frontend", "content_type": 7, "object_id": 1, "created_at": "2024-04-27T09:07:47.913Z", "modified_at": "2024-04-27T09:07:47.913Z"}}, {"model": "blog.comment", "pk": 3, "fields": {"creator": 1, "content": "some extras after changing everything to use crispy forms", "content_type": 7, "object_id": 1, "created_at": "2024-04-27T10:26:20.183Z", "modified_at": "2024-04-27T10:26:20.183Z"}}, {"model": "blog.tag", "pk": 1, "fields": {"value": "Django"}}, {"model": "blog.tag", "pk": 2, "fields": {"value": "Coursera"}}, {"model": "blog.post", "pk": 1, "fields": {"author": 1, "created_at": "2024-04-25T20:17:53.544Z", "modified_at": "2024-04-25T20:18:42.894Z", "published_at": "2024-04-25T20:16:00Z", "title": "New Post Title", "slug": "new-post-title", "summary": "This is new title summary", "content": "
this is text
", "tags": [1, 2]}}, {"model": "blog.post", "pk": 2, "fields": {"author": 1, "created_at": "2024-04-27T08:47:42.299Z", "modified_at": "2024-04-27T08:47:42.299Z", "published_at": "2024-04-27T08:46:32Z", "title": "Post 2", "slug": "post-2", "summary": "this is summary of post 2", "content": "this is post 2 text
", "tags": [1, 2]}}, {"model": "blog.post", "pk": 3, "fields": {"author": 1, "created_at": "2024-04-27T08:49:33.124Z", "modified_at": "2024-04-27T08:49:33.124Z", "published_at": "2024-04-23T08:48:00Z", "title": "Post 3", "slug": "post-3", "summary": "Post 3 dated 23rd", "content": "jlsjafkjsjsjsaiuroalaahhaofzoaujvahvahoao
", "tags": [1, 2]}}, {"model": "blango_auth.Userr", "pk": 1, "fields": {"password": "pbkdf2_sha256$720000$f9fIrc28qkOJIh8JDDNUBq$eRdASO3rp/4JnBGbKSeES1SqN2aDU4aUXOzOtQ+u3zo=", "last_login": "2024-04-25T20:15:49.518Z", "is_superuser": true, "username": "codio", "first_name": "", "last_name": "", "email": "", "is_staff": true, "is_active": true, "date_joined": "2024-04-25T20:09:08.771Z", "groups": [], "user_permissions": []}}] \ No newline at end of file diff --git a/blango/environ_test.py b/blango/environ_test.py new file mode 100644 index 0000000000..bc2aacb2b3 --- /dev/null +++ b/blango/environ_test.py @@ -0,0 +1,16 @@ +from os import environ + +environ.setdefault("PYTHON_DEFAULT", "Python Default") + +print(f"Value of 'MUST_BE_SET': '{environ['MUST_BE_SET']}'") +print(f"Value of 'PYTHON_DEFAULT': '{environ['PYTHON_DEFAULT']}'") + +try: + print(f"Value of 'ALWAYS_OVERRIDDEN' before override: '{environ['ALWAYS_OVERRIDDEN']}'") +except KeyError: + print("'ALWAYS_OVERRIDDEN' was not set.") + +environ["ALWAYS_OVERRIDDEN"] = "Always Overridden In Python" + +print(f"Value of 'ALWAYS_OVERRIDDEN' after override: '{environ['ALWAYS_OVERRIDDEN']}'") +print(f"Value of 'OPTIONAL': '{environ.get('OPTIONAL')}'") \ No newline at end of file diff --git a/blango/requests_test.py b/blango/requests_test.py new file mode 100644 index 0000000000..4099f107da --- /dev/null +++ b/blango/requests_test.py @@ -0,0 +1,32 @@ +import requests + +# put your real credentials here +EMAIL_ADDRESS = "codio@abc.com" +PASSWORD = "codio" +BASE_URL = "http://localhost:8000/" + +anon_post_resp = requests.get(BASE_URL + "api/v1/posts/") +anon_post_resp.raise_for_status() + +anon_post_count = anon_post_resp.json()["count"] +print(f"Anon users have {anon_post_count} post{'' if anon_post_count == 1 else 's'}") + +auth_resp = requests.post( + BASE_URL + "api/v1/token-auth/", + json={"username": EMAIL_ADDRESS, "password": PASSWORD}, +) +auth_resp.raise_for_status() +token = auth_resp.json()["token"] + +# Use the token in a request +authed_post_resp = requests.get( + BASE_URL + "api/v1/posts/", headers={"Authorization": f"Token {token}"} +) +authed_post_count = authed_post_resp.json()["count"] + +print( + f"Authenticated user has {authed_post_count} post{'' if authed_post_count == 1 else 's'}" +) + +# Since requests doesn't remember headers between requests, this next request is unauthenticated again +anon_post_resp = requests.get(BASE_URL + "api/v1/posts/") \ No newline at end of file diff --git a/blango/settings.py b/blango/settings.py index f9209bef27..b3cf806f59 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -1,125 +1,221 @@ -""" -Django settings for blango project. +""" +Django settings for blango project. +Generated by 'django-admin startproject' using Django 3.2.5. +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" -Generated by 'django-admin startproject' using Django 3.2.7. -For more information on this file, see -https://docs.djangoproject.com/en/3.2/topics/settings/ -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.2/ref/settings/ -""" +import os +from pathlib import Path +from datetime import timedelta -from pathlib import Path +from configurations import Configuration +from configurations import values -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent +class Dev(Configuration): -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ +# Build paths inside the project like this: BASE_DIR / 'subdir'. + BASE_DIR = Path(__file__).resolve().parent.parent -# 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 + # 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-&!=9y436&^-bc$qia-mxngyf&xx)@ct)8lu@)=qxg_07-=z01w' + # SECURITY WARNING: don't run with debug turned on in production! -ALLOWED_HOSTS = [] + DEBUG = True + ALLOWED_HOSTS = ['*', 'localhost'] -# Application definition -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', -] + AUTH_USER_MODEL = "blango_auth.User" -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] -ROOT_URLCONF = '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', - ], + #X_FRAME_OPTIONS = 'ALLOW-FROM ' + os.environ.get('CODIO_HOSTNAME') + '-8000.codio.io' + CSRF_COOKIE_SAMESITE = None + #CSRF_TRUSTED_ORIGINS = [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 = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'blog', + 'crispy_forms', + 'crispy_bootstrap5', + 'blango_auth', + 'rest_framework', + 'rest_framework.authtoken', + 'drf_yasg', + 'django_filters', + 'versatileimagefield', + ] + + + 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', + + ] + + ''' + Reminder: MIDDLEWARE COMMENT OUT these changes only apply to working with Django on Codio. Do + not make these changes to a project you plan on making available on the + internet. + ''' + + 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', + ], + }, + + }, + ] + + + WSGI_APPLICATION = 'blango.wsgi.application' + + + # Database + # https://docs.djangoproject.com/en/3.2/ref/settings/#databases + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } + + + # 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', }, - }, -] + ] -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' + TIME_ZONE = ("UTC") + USE_I18N = True + USE_L10N = True + USE_TZ = True -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} + # Static files (CSS, JavaScript, Images) + # https://docs.djangoproject.com/en/3.2/howto/static-files/ + + STATIC_URL = '/static/' + + MEDIA_ROOT = BASE_DIR / "media" + MEDIA_URL = "/media/" -# Password validation -# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + # Default primary key field type + # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field -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', - }, -] + DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" + CRISPY_TEMPLATE_PACK = "bootstrap5" -# Internationalization -# https://docs.djangoproject.com/en/3.2/topics/i18n/ + REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.TokenAuthentication", + ], -LANGUAGE_CODE = 'en-us' + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticatedOrReadOnly" + ], -TIME_ZONE = 'UTC' + #throttling + "DEFAULT_THROTTLE_CLASSES": [ + "blog.api.throttling.AnonSustainedThrottle", + "blog.api.throttling.AnonBurstThrottle", + "blog.api.throttling.UserSustainedThrottle", + "blog.api.throttling.UserBurstThrottle", + ], + "DEFAULT_THROTTLE_RATES": { + "anon_sustained": "500/day", + "anon_burst": "10/minute", + "user_sustained": "5000/day", + "user_burst": "100/minute", + }, + + #pagination + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 100, -USE_I18N = True -USE_L10N = True + #extending with django filters for rest rest_framework + "DEFAULT_FILTER_BACKENDS": [ + "django_filters.rest_framework.DjangoFilterBackend", + "rest_framework.filters.OrderingFilter", + ], -USE_TZ = True + #extending with JWT Json web tokens + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.TokenAuthentication", + "rest_framework_simplejwt.authentication.JWTAuthentication" + ], -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ + } -STATIC_URL = '/static/' -# Default primary key field type -# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + SWAGGER_SETTINGS = { + "SECURITY_DEFINITIONS": { + "Token": {"type": "apiKey", "name": "Authorization", "in": "header"}, + "Basic": {"type": "basic"}, + } + } \ No newline at end of file diff --git a/blango/settings_orig.py b/blango/settings_orig.py new file mode 100644 index 0000000000..62acdb3720 --- /dev/null +++ b/blango/settings_orig.py @@ -0,0 +1,160 @@ +""" +Django settings for blango project. + +Generated by 'django-admin startproject' using Django 3.2.7. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +import os +from pathlib import Path +#from configurations import Configuration +#from configurations import values + +class Dev(Configuration): + +# 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 = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'blog', + 'crispy_forms', + 'crispy_bootstrap5', + 'debug_toolbar', +] + +#INTERNAL_IPS = ["192.168.10.93"] + +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', + 'debug_toolbar.middleware.DebugToolbarMiddleware', +] +''' +Reminder: MIDDLEWARE COMMENT OUT these changes only apply to working with Django on Codio. Do +not make these changes to a project you plan on making available on the +internet. +''' + +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', + ], + }, + }, +] + +WSGI_APPLICATION = 'blango.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# 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', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5' +CRISPY_TEMPLATE_PACK = 'bootstrap5' + +#....this is set only for the course, remove when finished +''' +Reminder: these changes only apply to working with Django on Codio. Do +not make these changes to a project you plan on making available on the +internet. +''' +# X_FRAME_OPTIONS = 'ALLOW-FROM ' + os.environ.get('CODIO_HOSTNAME') + '-8000.codio.io' +# CSRF_COOKIE_SAMESITE = None +# CSRF_TRUSTED_ORIGINS = [os.environ.get('CODIO_HOSTNAME') + '-8000.codio.io'] +# CSRF_COOKIE_SECURE = True +# SESSION_COOKIE_SECURE = True +# CSRF_COOKIE_SAMESITE = 'None' +# SESSION_COOKIE_SAMESITE = 'None' \ No newline at end of file diff --git a/blango/urls.py b/blango/urls.py index cde05802f9..589c88e692 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -14,8 +14,31 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.conf.urls.static import static + +from django.urls import path, include +#import debug_toolbar +import blog.views +import blango_auth.views urlpatterns = [ path('admin/', admin.site.urls), + path("", blog.views.index), + path("post/Logged in as {{ request.user }}.
+ +{% endcol %} +{% endrow %} +{% endblock content %} \ 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..2b2f392f9f --- /dev/null +++ b/blango_auth/templates/registration/login.html @@ -0,0 +1,31 @@ +{% 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" %} + + +{% 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..36a76d2034 --- /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") diff --git a/blog/__init__.py b/blog/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blog/__pycache__/__init__.cpython-311.pyc b/blog/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000..b4f9abca7f Binary files /dev/null and b/blog/__pycache__/__init__.cpython-311.pyc differ diff --git a/blog/__pycache__/admin.cpython-311.pyc b/blog/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000000..11db98946e Binary files /dev/null and b/blog/__pycache__/admin.cpython-311.pyc differ diff --git a/blog/__pycache__/apps.cpython-311.pyc b/blog/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000000..b452d18c5c Binary files /dev/null and b/blog/__pycache__/apps.cpython-311.pyc differ diff --git a/blog/__pycache__/forms.cpython-311.pyc b/blog/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000..4c1ed74350 Binary files /dev/null and b/blog/__pycache__/forms.cpython-311.pyc differ diff --git a/blog/__pycache__/models.cpython-311.pyc b/blog/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000..4bcf19d77a Binary files /dev/null and b/blog/__pycache__/models.cpython-311.pyc differ diff --git a/blog/__pycache__/views.cpython-311.pyc b/blog/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000000..79071ce1c3 Binary files /dev/null and b/blog/__pycache__/views.cpython-311.pyc differ diff --git a/blog/admin.py b/blog/admin.py new file mode 100644 index 0000000000..c7631bbbf1 --- /dev/null +++ b/blog/admin.py @@ -0,0 +1,20 @@ +from django.contrib import admin + +# Register your models here. + + +from blog.models import Tag, Post, Comment, AuthorProfile + +class PostAdmin(admin.ModelAdmin): + prepopulated_fields = {"slug": ("title",)} + list_display = ('slug', 'published_at') + + + +admin.site.register(Tag) +admin.site.register(Post, PostAdmin) +admin.site.register(Comment) + +admin.site.register(AuthorProfile) + + diff --git a/blog/api/__init__.py b/blog/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blog/api/__pycache__/__init__.cpython-311.pyc b/blog/api/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000..bd8186ba5e Binary files /dev/null and b/blog/api/__pycache__/__init__.cpython-311.pyc differ diff --git a/blog/api/__pycache__/filters.cpython-311.pyc b/blog/api/__pycache__/filters.cpython-311.pyc new file mode 100644 index 0000000000..974a585a06 Binary files /dev/null and b/blog/api/__pycache__/filters.cpython-311.pyc differ diff --git a/blog/api/__pycache__/permissions.cpython-311.pyc b/blog/api/__pycache__/permissions.cpython-311.pyc new file mode 100644 index 0000000000..302f1d162a Binary files /dev/null and b/blog/api/__pycache__/permissions.cpython-311.pyc differ diff --git a/blog/api/__pycache__/serializers.cpython-311.pyc b/blog/api/__pycache__/serializers.cpython-311.pyc new file mode 100644 index 0000000000..f9b3342d7d Binary files /dev/null and b/blog/api/__pycache__/serializers.cpython-311.pyc differ diff --git a/blog/api/__pycache__/throttling.cpython-311.pyc b/blog/api/__pycache__/throttling.cpython-311.pyc new file mode 100644 index 0000000000..312a27eac7 Binary files /dev/null and b/blog/api/__pycache__/throttling.cpython-311.pyc differ diff --git a/blog/api/__pycache__/urls.cpython-311.pyc b/blog/api/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000..93656a421a Binary files /dev/null and b/blog/api/__pycache__/urls.cpython-311.pyc differ diff --git a/blog/api/__pycache__/views.cpython-311.pyc b/blog/api/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000000..6b75e86f46 Binary files /dev/null and b/blog/api/__pycache__/views.cpython-311.pyc differ diff --git a/blog/api/filters.py b/blog/api/filters.py new file mode 100644 index 0000000000..e55c6af26b --- /dev/null +++ b/blog/api/filters.py @@ -0,0 +1,31 @@ +from django_filters import rest_framework as filters + +from blog.models import Post + + +class PostFilterSet(filters.FilterSet): + published_from = filters.DateFilter( + field_name="published_at", lookup_expr="gte", label="Published Date From" + ) + published_to = filters.DateFilter( + field_name="published_at", lookup_expr="lte", label="Published Date To" + ) + author_email = filters.CharFilter( + field_name="author__email", + lookup_expr="icontains", + label="Author Email Contains", + ) + summary = filters.CharFilter( + field_name="summary", + lookup_expr="icontains", + label="Summary Contains", + ) + content = filters.CharFilter( + field_name="content", + lookup_expr="icontains", + label="Content Contains", + ) + + class Meta: + model = Post + fields = ["author", "tags"] diff --git a/blog/api/permissions.py b/blog/api/permissions.py new file mode 100644 index 0000000000..606cba90f1 --- /dev/null +++ b/blog/api/permissions.py @@ -0,0 +1,13 @@ +from rest_framework import permissions + + +class AuthorModifyOrReadOnly(permissions.IsAuthenticatedOrReadOnly): + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + + return request.user == obj.author + +class IsAdminUserForObject(permissions.IsAdminUser): + def has_object_permission(self, request, view, obj): + return bool(request.user and request.user.is_staff) diff --git a/blog/api/serializers.py b/blog/api/serializers.py new file mode 100644 index 0000000000..5e5fa98d8e --- /dev/null +++ b/blog/api/serializers.py @@ -0,0 +1,99 @@ +from rest_framework import serializers +from blog.models import Post, Tag, Comment + +from blango_auth.models import User +from versatileimagefield.serializers import VersatileImageFieldSerializer + + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["first_name", "last_name", "email"] + + + + + +class CommentSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + creator = UserSerializer(read_only=True) + + class Meta: + model = Comment + fields = ["id", "creator", "content", "modified_at", "created_at"] + readonly = ["modified_at", "created_at"] + + + + +class PostSerializer(serializers.ModelSerializer): + tags = serializers.SlugRelatedField( + slug_field="value", many=True, queryset=Tag.objects.all() + ) + + author = serializers.HyperlinkedRelatedField( + queryset=User.objects.all(), view_name="api_user_detail", lookup_field="email" + ) + + hero_image = VersatileImageFieldSerializer( + sizes=[ + ("full_size", "url"), + ("thumbnail", "thumbnail__100x100"), + ], + read_only=True, + ) + + + class Meta: + model = Post + #fields = "__all__" + exclude = ["ppoi"] + + readonly = ["modified_at", "created_at"] + + + +class TagField(serializers.SlugRelatedField): + def to_internal_value(self, data): + try: + return self.get_queryset().get_or_create(value=data.lower())[0] + except (TypeError, ValueError): + self.fail(f"Tag value {data} is invalid") + + +class TagSerializer(serializers.ModelSerializer): + class Meta: + model = Tag + fields = "__all__" + + + +class PostDetailSerializer(PostSerializer): + comments = CommentSerializer(many=True) + + hero_image = VersatileImageFieldSerializer(sizes=[ + ("full_size", "url"), + ("thumbnail", "thumbnail__100x100"), + ("square_crop", "crop__200x200"), + ], + read_only=True, + ) + + def update(self, instance, validated_data): + comments = validated_data.pop("comments") + + + + instance = super(PostDetailSerializer, self).update(instance, validated_data) + + for comment_data in comments: + if comment_data.get("id"): + # comment has an ID so was pre-existing + continue + comment = Comment(**comment_data) + comment.creator = self.context["request"].user + comment.content_object = instance + comment.save() + + return instance \ No newline at end of file diff --git a/blog/api/throttling.py b/blog/api/throttling.py new file mode 100644 index 0000000000..41baa64d6e --- /dev/null +++ b/blog/api/throttling.py @@ -0,0 +1,18 @@ +from rest_framework.throttling import AnonRateThrottle, UserRateThrottle + + + +class AnonSustainedThrottle(AnonRateThrottle): + scope = "anon_sustained" + + +class AnonBurstThrottle(AnonRateThrottle): + scope = "anon_burst" + + +class UserSustainedThrottle(UserRateThrottle): + scope = "user_sustained" + + +class UserBurstThrottle(UserRateThrottle): + scope = "user_burst" \ No newline at end of file diff --git a/blog/api/urls.py b/blog/api/urls.py new file mode 100644 index 0000000000..0cb862ea2c --- /dev/null +++ b/blog/api/urls.py @@ -0,0 +1,68 @@ +from django.urls import path, include, re_path +from drf_yasg import openapi +from drf_yasg.views import get_schema_view +import os + +from rest_framework.routers import DefaultRouter + +from rest_framework.urlpatterns import format_suffix_patterns +from rest_framework.authtoken import views + +#adding JWT imports +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView + + + + +#from blog.api.views import PostList, PostDetail, UserDetail, TagViewSet +from blog.api.views import UserDetail, TagViewSet, PostViewSet + + + +router = DefaultRouter() +router.register("tags", TagViewSet) +router.register("posts", PostViewSet) + + +urlpatterns = [ + #path("posts/", PostList.as_view(), name="api_post_list"), + #path("posts/this is text
", "tags": [1, 2]}}, {"model": "blog.post", "pk": 2, "fields": {"author": 1, "created_at": "2024-04-27T08:47:42.299Z", "modified_at": "2024-04-27T08:47:42.299Z", "published_at": "2024-04-27T08:46:32Z", "title": "Post 2", "slug": "post-2", "summary": "this is summary of post 2", "content": "this is post 2 text
", "tags": [1, 2]}}, {"model": "blog.post", "pk": 3, "fields": {"author": 1, "created_at": "2024-04-27T08:49:33.124Z", "modified_at": "2024-04-27T08:49:33.124Z", "published_at": "2024-04-23T08:48:00Z", "title": "Post 3", "slug": "post-3", "summary": "Post 3 dated 23rd", "content": "jlsjafkjsjsjsaiuroalaahhaofzoaujvahvahoao
", "tags": [1, 2]}}, {"model": "blango_auth.Userr", "pk": 1, "fields": {"password": "pbkdf2_sha256$720000$f9fIrc28qkOJIh8JDDNUBq$eRdASO3rp/4JnBGbKSeES1SqN2aDU4aUXOzOtQ+u3zo=", "last_login": "2024-04-25T20:15:49.518Z", "is_superuser": true, "username": "codio", "first_name": "", "last_name": "", "email": "", "is_staff": true, "is_active": true, "date_joined": "2024-04-25T20:09:08.771Z", "groups": [], "user_permissions": []}}] \ No newline at end of file diff --git a/blog/forms.py b/blog/forms.py new file mode 100644 index 0000000000..96ec088576 --- /dev/null +++ b/blog/forms.py @@ -0,0 +1,17 @@ +from django import forms +from crispy_forms.layout import Submit +from crispy_forms.helper import FormHelper + + +from blog.models import Comment + + +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')) diff --git a/blog/migrations/0001_initial.py b/blog/migrations/0001_initial.py new file mode 100644 index 0000000000..0a6ab378d8 --- /dev/null +++ b/blog/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 5.0.4 on 2024-04-25 20:12 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='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..948f72985b --- /dev/null +++ b/blog/migrations/0002_comment.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.4 on 2024-04-27 07:59 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0001_initial'), + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + 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()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('modified_at', models.DateTimeField(auto_now=True)), + ('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_alter_tag_options_alter_comment_created_at_and_more.py b/blog/migrations/0003_alter_tag_options_alter_comment_created_at_and_more.py new file mode 100644 index 0000000000..d653109b1f --- /dev/null +++ b/blog/migrations/0003_alter_tag_options_alter_comment_created_at_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 5.0.4 on 2024-05-25 08:00 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0002_comment'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterModelOptions( + name='tag', + options={'ordering': ['value']}, + ), + 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), + ), + migrations.AlterField( + model_name='post', + name='published_at', + field=models.DateTimeField(blank=True, db_index=True, null=True), + ), + migrations.AlterField( + model_name='tag', + name='value', + field=models.TextField(max_length=100, unique=True), + ), + 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/0004_post_hero_image_post_ppoi.py b/blog/migrations/0004_post_hero_image_post_ppoi.py new file mode 100644 index 0000000000..2264cd4536 --- /dev/null +++ b/blog/migrations/0004_post_hero_image_post_ppoi.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.4 on 2024-05-26 15:02 + +import versatileimagefield.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0003_alter_tag_options_alter_comment_created_at_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='post', + name='hero_image', + field=versatileimagefield.fields.VersatileImageField(blank=True, null=True, upload_to='hero_images'), + ), + migrations.AddField( + model_name='post', + name='ppoi', + field=versatileimagefield.fields.PPOIField(blank=True, default='0.5x0.5', editable=False, max_length=20, null=True), + ), + ] 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-311.pyc b/blog/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000..a06868eb6c Binary files /dev/null and b/blog/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/blog/migrations/__pycache__/0002_comment.cpython-311.pyc b/blog/migrations/__pycache__/0002_comment.cpython-311.pyc new file mode 100644 index 0000000000..d4d38a0248 Binary files /dev/null and b/blog/migrations/__pycache__/0002_comment.cpython-311.pyc differ diff --git a/blog/migrations/__pycache__/0003_alter_tag_options_alter_comment_created_at_and_more.cpython-311.pyc b/blog/migrations/__pycache__/0003_alter_tag_options_alter_comment_created_at_and_more.cpython-311.pyc new file mode 100644 index 0000000000..77ed6504a8 Binary files /dev/null and b/blog/migrations/__pycache__/0003_alter_tag_options_alter_comment_created_at_and_more.cpython-311.pyc differ diff --git a/blog/migrations/__pycache__/0004_post_hero_image_post_ppoi.cpython-311.pyc b/blog/migrations/__pycache__/0004_post_hero_image_post_ppoi.cpython-311.pyc new file mode 100644 index 0000000000..7b8466c31b Binary files /dev/null and b/blog/migrations/__pycache__/0004_post_hero_image_post_ppoi.cpython-311.pyc differ diff --git a/blog/migrations/__pycache__/__init__.cpython-311.pyc b/blog/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000..1e0378c9ab Binary files /dev/null and b/blog/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/blog/models.py b/blog/models.py new file mode 100644 index 0000000000..477782627b --- /dev/null +++ b/blog/models.py @@ -0,0 +1,59 @@ +from django.db import models +from django.conf import settings +from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey +from django.contrib.contenttypes.models import ContentType + +#versatile immage +from versatileimagefield.fields import VersatileImageField, PPOIField + + + + +class Tag(models.Model): + value = models.TextField(max_length=100, unique=True) + + class Meta: + ordering = ["value"] + + 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) + hero_image = VersatileImageField(upload_to="hero_images", ppoi_field="ppoi", null=True, blank=True) + ppoi = PPOIField(null=True, blank=True) + + + 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}" + diff --git a/blog/static/blog/blog copy.js b/blog/static/blog/blog copy.js new file mode 100644 index 0000000000..cbe2fddb79 --- /dev/null +++ b/blog/static/blog/blog copy.js @@ -0,0 +1,232 @@ + + +//REACTJS CODE STARTS HERE +class ClickButton extends React.Component { + state = { + wasClicked: false + } + + handleClick () { + this.setState( + {wasClicked: true} + ) + } + + + +//this is JSX + render () { + let buttonText + + if (this.state.wasClicked) + buttonText = 'Clicked!' + else + buttonText = 'Click Me' + + return + } +//end JSX + + + /* + render () { + let buttonText + + if (this.state.wasClicked) + buttonText = 'Clicked!' + else + buttonText = 'Click Me' + return React.createElement( + 'button', + { + className: 'btn btn-primary mt-2', + onClick: () => { + this.handleClick() + } + }, + buttonText + ) + }*/ + +} + +//mount the reactjs code to the html +const domContainer = document.getElementById('react_root') +ReactDOM.render( + React.createElement(ClickButton), + domContainer +) + + +//REACTJS ENDS HERE + + + +/* + // CONSOLE JAVASCRIPT - BACKEND ONLY STARTS HERE + +function resolvedCallback(data) { + console.log('Resolved with data ' + data) +} + +function rejectedCallback(message) { + console.log('Rejected with message ' + message) +} + +const lazyAdd = function (a, b) { + const doAdd = (resolve, reject) => { + if (typeof a !== "number" || typeof b !== "number") { + reject("a and b must both be numbers") + } else { + const sum = a + b + resolve(sum) + } + } + + return new Promise(doAdd) +} + +const p = lazyAdd(3, 4) +p.then(resolvedCallback, rejectedCallback) + +lazyAdd("nan", "alsonan").then(resolvedCallback, rejectedCallback) + + + + + + +const theNumber = 1 +let yourName = 'Ben' + +if (theNumber === 1) { + let yourName = 'Leo' + alert(yourName) +} + +alert(yourName) + + +console.time('myTimer') +console.count('counter1') +console.log('A normal log message') +console.warn('Warning: something bad might happen') +console.error('Something bad did happen!') +console.count('counter1') +console.log('All the things above took this long to happen:') +console.timeEnd('myTimer') + + + +function sayHello(yourName) { + if (yourName === undefined) { + console.log('Hello, no name') + } else { + console.log('Hello, ' + yourName) + } +} + +const yourName = 'Your Name' // Put your name here + +console.log('Before setTimeout') + +setTimeout(() => { + sayHello(yourName) + }, 2000 +) + +console.log('After setTimeout') + + + +for(let i = 0; i < 10; i += 1) { + console.log('for loop i: ' + i) +} + +let j = 0 +while(j < 10) { + console.log('while loop j: ' + j) + j += 1 +} + +let k = 10 + +do { + console.log('do while k: ' + k) +} while(k < 10) + +const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +numbers.forEach((value => { + console.log('For each value ' + value) +})) + +const doubled = numbers.map(value => value * 2) + +console.log('Here are the doubled numbers') + +console.log(doubled) + + + +class Greeter { + constructor (name) { + this.name = name + } + + getGreeting () { + if (this.name === undefined) { + return 'Hello, no name' + } + + return 'Hello, ' + this.name + } + + showGreeting (greetingMessage) { + console.log(greetingMessage) + } + + greet () { + this.showGreeting(this.getGreeting()) + } +} + +const g = new Greeter('Patchy') // Put your name here if you like +g.greet() + + + +class DelayedGreeter extends Greeter { + delay = 2000 + + constructor (name, delay) { + super(name) + if (delay !== undefined) { + this.delay = delay + } + } + + greet () { + setTimeout( + () => { + this.showGreeting(this.getGreeting()) + }, this.delay + ) + } +} + +const dg2 = new DelayedGreeter('Patso 2 Seconds') +dg2.greet() + +const dg1 = new DelayedGreeter('Patchu 1 Second', 1000) +dg1.greet() +*/ \ No newline at end of file diff --git a/blog/static/blog/blog.js b/blog/static/blog/blog.js new file mode 100644 index 0000000000..5e9b8a233b --- /dev/null +++ b/blog/static/blog/blog.js @@ -0,0 +1,150 @@ +/*this is the fetch and react hooks + +['/api/v1/posts/', '/', '/abadurl/'].forEach(url => { + fetch(url).then(response => { + if (response.status !== 200) { + throw new Error('Invalid status from server: ' + response.statusText) + } + + return response.json() + }).then(data => { + // do something with data, for example + console.log(data) + }).catch(e => { + console.error(e) + }) +}) + +*/ + + + +//this is the post-table code, do not remove +//this is the post-table code, do not remove + +class PostRow extends React.Component { + render () { + const post = this.props.post + + let thumbnail + + if (post.hero_image.thumbnail) { + thumbnail = + } else { + thumbnail = '-' + } + + returnTitle | +Image | +Tags | +Slug | +Summary | +Link | +
---|
{{ post.summary }}
+({{ post.content|wordcount }} words)Read More +
+{{ 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 %} +{{ post.author.profile.bio }}
+{% endcol %} +{% endrow %} +{% endif %} + +{% include "blog/post-comments.html" %} + + +{% row %} +{% col %} + {% recent_posts post %} +{% 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..5343856bb4 --- /dev/null +++ b/templates/blog/post-list.html @@ -0,0 +1,6 @@ +