From 4e04cdfba2003ce06b7367a88274ba0a25bcc567 Mon Sep 17 00:00:00 2001 From: distance Date: Thu, 25 Apr 2024 22:54:51 +0200 Subject: [PATCH 01/46] Finish Introduction and Django Admin --- blango/settings.py | 29 +++++++++++-- blog/__init__.py | 0 blog/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 154 bytes blog/__pycache__/admin.cpython-311.pyc | Bin 0 -> 806 bytes blog/__pycache__/apps.cpython-311.pyc | Bin 0 -> 521 bytes blog/__pycache__/models.cpython-311.pyc | Bin 0 -> 1894 bytes blog/admin.py | 18 ++++++++ blog/apps.py | 6 +++ blog/migrations/0001_initial.py | 39 ++++++++++++++++++ blog/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 2168 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 165 bytes blog/models.py | 25 +++++++++++ blog/tests.py | 3 ++ blog/views.py | 3 ++ db.sqlite3 | Bin 0 -> 163840 bytes 16 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 blog/__init__.py create mode 100644 blog/__pycache__/__init__.cpython-311.pyc create mode 100644 blog/__pycache__/admin.cpython-311.pyc create mode 100644 blog/__pycache__/apps.cpython-311.pyc create mode 100644 blog/__pycache__/models.cpython-311.pyc create mode 100644 blog/admin.py create mode 100644 blog/apps.py create mode 100644 blog/migrations/0001_initial.py create mode 100644 blog/migrations/__init__.py create mode 100644 blog/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 blog/migrations/__pycache__/__init__.cpython-311.pyc create mode 100644 blog/models.py create mode 100644 blog/tests.py create mode 100644 blog/views.py create mode 100644 db.sqlite3 diff --git a/blango/settings.py b/blango/settings.py index f9209bef27..5bb5242b17 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -11,6 +11,7 @@ """ from pathlib import Path +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -25,7 +26,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['*'] # Application definition @@ -37,17 +38,23 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'blog', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', + # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +''' +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' @@ -123,3 +130,19 @@ # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +#....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/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 0000000000000000000000000000000000000000..60abe4f9f8c77b77407d238acb8b96254ee715da GIT binary patch literal 154 zcmZ3^%ge<81oyXVrGeezw6n?(*rPq+6Kn9{Bp%!(yf-<8DA)ywr&=cB$G4d;}C=!uZCbpn3b;9qGE1)hs`#pcp&)>7(^T(y7IvCMk*7qI}fbZy3 z;>^MHvjL~TfDr)+@d-g}2`jOEJE1-$V8L@>_AxNZ$$rIyp$%|ubS8ey#1<;=%&pMh zE4PQ{73W50Lf}6*l-GuqVHvGh%y|V~?FYIZ(N4sYxbIooejW8ReVM9!cYd^7D&ln6 zxTnCuCk%Xx5oX22x0&rzMomITFCzCTX46!OK@XGXaW1%LNi0Y=vO!nG>Mdtsl8PZ|ImRxKNVfar|pt-^QJ?E#-x#H*wCj zD|yeB(K7vQgD&02vqn6TZR6r+tM|rCY(mHwR*HY;#07G{fYs}BLM`%e0@uxn wGit8<#K5mV(I$}Eas3;#zC!C{^8(rsBsdLb!N>e~Z{ z(xgNwGGi`rh0^QT=6R&X9M_KdklL6}-K>C&J;OqNY^k^Rt&7MBBkSzQ>WCYk3!6n& zkIOHh5uRlSL1YA@P);dT8iJ^SYW%ZijlHQ>pOv@99l65lirdxIq?|V-3x#riS*eOd vDRBeQvUrg7a2Rf)E5{a{qMECOkQwYufA9I4LTYpPtL_`0^7iYWQ2N+6Insdf literal 0 HcmV?d00001 diff --git a/blog/__pycache__/models.cpython-311.pyc b/blog/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..891462dfc66ff436810ca1e0d96ee8e791376deb GIT binary patch literal 1894 zcma(SO-~zF@U6Y;uU(tiCPZlnC5=Kvi+l0J?f`L2D4~K*szN~!kPhg9YOoL#NPsL88vKD!kqJ9uPx%C_ zy@J<=g5*g_AQk!-sSpjXlj+DCRGJptP}QVNw}6UR%(g2=+1k1^=zV(TyK}%E6GjRG zC51qMu;8^LRA>fH1aJmk9|{t>TL@7R4wKwqUMs($u=P@KV^wWW)tOPYx3{k3VGC(I zt$PE|G5MZgN_|3b%~kb429GkAOc?BRu|{>$8A%@=v?8~=c<}0U*o~;Fp;eix zx-nG+J2f3?MODA8X}Z6|(Q7xFXM6Tih3S+lz;le^CE+QgM-YX22vLm5NC_sOSNd5JUH%DVzkL zfbjZIhJ@9-rX&`o4NQz<0~gV@zb93;itdP{ju1JueIZAPJFGt z)JZ-#8gr5l&K|Xr^X=rklbo-A*-1?uO*yHlGqIJLZKr0P)NK7pC;jkf&PhK!rN550 z(u?i%qLW^%KYi~&cjP;bIY++JmhU?9-Fmi@m}r!o#Kh_SRwC0*WSm5%zT8`ECnlZ5 zB=GV*RJ!-`{j=qkywH{x9C@Lh{g2B5W5(+%N_X%Y%+O80tnwuf2`%|`l~(W%z`vcm zboHn3U1Y((^%nUfu!&c$^&_D3pdbhxGT!QcI%Kld|D21W0JqpMYD^u@yv6m4>&=9M Tvcsq`cewBt*Dv0)>DBu$phc?s literal 0 HcmV?d00001 diff --git a/blog/admin.py b/blog/admin.py new file mode 100644 index 0000000000..50a070b82e --- /dev/null +++ b/blog/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin + +# Register your models here. + +from blog.models import Tag, Post + +class PostAdmin(admin.ModelAdmin): + prepopulated_fields = {"slug": ("title",)} + list_display = ('slug', 'published_at') + + + + + +admin.site.register(Tag) +admin.site.register(Post, PostAdmin) + + 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/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/__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 0000000000000000000000000000000000000000..d94e1c449ca2edf6ee5cad552d565f1d96ec8622 GIT binary patch literal 2168 zcmb7FO;FoL6qaOLmW=_M0FI3lLnhP4gmKbI(o6~cp@8WRBurwa9S<3e%r1%?S#l-W zgq%3^&|_}pm=kR$9CGM^o|lqz9d&*2txj7R~IrXh%5dvwaU1?YQ_I>-_+xN5j zrN2MUK|A-yTj?|*P{oHPdt^O~2jjH(6Mz|~F#+@o>dJALAO8BgwTfJRv~wwF7OQsc+trwWx3 zbOy5aER8J(8Cs^HvZ7K!LnSJFJzogZgwk|OdELT_gAwg(SXy1Nw&eSGi^gnhX^O5r z#5B>uR@JnzY$$b1BP~U5;+F_af?9~zl?Sqp4b`~^)e!a&;)aHGWYh49X*)FD6E7YizVO)=Aup)9e3so?o>DBMmUkekfISQ!T;@;1U+r znH#)QTIb++Hw&Xwl090MX1UZWB5XmsWFt@6TL znGP;1##Y&6V{fn7z`e`tH|{kUz`dE7n_gRmRIt~U7uM#@O+z;oWUsMss3v?&b?xm9 zxOVyS<&Wf^H;Rodi`}T+Ai>1`6C$*I_`Dbx+FtC6T zPuEFyx}BYNv(qGbqZGSoiDlh z5=kz0QfJBd)yJYcesv#_@wxW+oI5^8lJlKZo=jYOJnBwd+n;~tkcp-C#F9I)M3T$@ zXlEytCYi#{sGBLYGZ)>=MUtHAq_QM8wNr9)Q~M#3n`!4}+}sRFetLw_&K2EUkt8p5 zQW-Kfwezt%Hubdi`<8os(Y?OnUjLrl#qQlE8Edu2TJBhjBsV=(mzb(c``R9ul{|VUIN(M6@8$jGtabR4w{{zG9GL))j!!z8)WH za>Flx*x?SZR=gzlljIX7*AspNW^be!zt%ppJ_nNh3T+35Ziwf3$QgcuaO3{l2_{EY eE)ecLZ&%FoVwXEPJc_%?^RF1=;5}T=s=on!^Fqr2 literal 0 HcmV?d00001 diff --git a/blog/migrations/__pycache__/__init__.cpython-311.pyc b/blog/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82cfc6ac1a3c308e04e95a8426158b55600d0548 GIT binary patch literal 165 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 Date: Fri, 26 Apr 2024 10:44:07 +0200 Subject: [PATCH 02/46] version2 --- blango/settings.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/blango/settings.py b/blango/settings.py index 5bb5242b17..d76190a1db 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -61,7 +61,7 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -137,11 +137,9 @@ 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' +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_TRUSTED_ORIGINS = [os.environ.get('CODIO_HOSTNAME') + '-8000.codio.io'] CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SAMESITE = 'None' From 49a089f40121c499d3d3eccd40c93d926838c5ab Mon Sep 17 00:00:00 2001 From: montagu101 <127699435+montagu101@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:06:21 +0200 Subject: [PATCH 03/46] Create templates --- templates | 1 + 1 file changed, 1 insertion(+) create mode 100644 templates diff --git a/templates b/templates new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/templates @@ -0,0 +1 @@ + From 927e024bd6e126dfca81cf2ca073a41003ca702e Mon Sep 17 00:00:00 2001 From: distance Date: Fri, 26 Apr 2024 11:09:18 +0200 Subject: [PATCH 04/46] changed --- templates | 1 - 1 file changed, 1 deletion(-) delete mode 100644 templates diff --git a/templates b/templates deleted file mode 100644 index 8b13789179..0000000000 --- a/templates +++ /dev/null @@ -1 +0,0 @@ - From c3334e181c3731300a8b1bd7305499c86df91632 Mon Sep 17 00:00:00 2001 From: distance Date: Fri, 26 Apr 2024 11:31:44 +0200 Subject: [PATCH 05/46] Finish html frameworks --- blango/urls.py | 3 +++ blog/views.py | 2 ++ templates/base.py | 18 ++++++++++++++++++ templates/blog/index.html | 5 +++++ 4 files changed, 28 insertions(+) create mode 100644 templates/base.py create mode 100644 templates/blog/index.html diff --git a/blango/urls.py b/blango/urls.py index cde05802f9..16719906b3 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -16,6 +16,9 @@ from django.contrib import admin from django.urls import path +import blog.views + urlpatterns = [ path('admin/', admin.site.urls), + path("", blog.views.index) ] diff --git a/blog/views.py b/blog/views.py index 91ea44a218..9a5bffd5b2 100644 --- a/blog/views.py +++ b/blog/views.py @@ -1,3 +1,5 @@ from django.shortcuts import render # Create your views here. +def index(request): + return render(request, "blog/index.html") diff --git a/templates/base.py b/templates/base.py new file mode 100644 index 0000000000..48e8a280d4 --- /dev/null +++ b/templates/base.py @@ -0,0 +1,18 @@ + + + + + + + + Hello, world! + + +

Hello, world!

+ {% 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..47997275b7 --- /dev/null +++ b/templates/blog/index.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block content %} +

Index Template

+{% endblock %} \ No newline at end of file From f560db23f1e3b3f55db206997f36df96d6e781f8 Mon Sep 17 00:00:00 2001 From: distance Date: Fri, 26 Apr 2024 11:46:19 +0200 Subject: [PATCH 06/46] update --- templates/base.py | 18 ------------------ templates/index.html | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 18 deletions(-) delete mode 100644 templates/base.py create mode 100644 templates/index.html diff --git a/templates/base.py b/templates/base.py deleted file mode 100644 index 48e8a280d4..0000000000 --- a/templates/base.py +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - Hello, world! - - -

Hello, world!

- {% block content %} - {% endblock %} - - - \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000000..234177377b --- /dev/null +++ b/templates/index.html @@ -0,0 +1,21 @@ + + + + + + + Hello, world! + + +

Hello World

+ {% block content %} + + {% endblock %} + + + + + + \ No newline at end of file From 3fcfb5fc6d813254f419c5ee1280f7e6abd64cc5 Mon Sep 17 00:00:00 2001 From: distance Date: Fri, 26 Apr 2024 11:47:43 +0200 Subject: [PATCH 07/46] namechange --- templates/{index.html => base.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/{index.html => base.html} (100%) diff --git a/templates/index.html b/templates/base.html similarity index 100% rename from templates/index.html rename to templates/base.html From e20bb61b128f11e94cfafdfa8261ad006cbc29ac Mon Sep 17 00:00:00 2001 From: distance Date: Fri, 26 Apr 2024 12:19:56 +0200 Subject: [PATCH 08/46] templateTags --- blog/templatetags/__init__.py | 0 blog/templatetags/blog_extras.py | 19 +++++++++++++++++++ blog/tests.py | 3 --- blog/views.py | 5 ++++- templates/blog/index.html | 17 ++++++++++++++++- 5 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 blog/templatetags/__init__.py create mode 100644 blog/templatetags/blog_extras.py delete mode 100644 blog/tests.py diff --git a/blog/templatetags/__init__.py b/blog/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blog/templatetags/blog_extras.py b/blog/templatetags/blog_extras.py new file mode 100644 index 0000000000..8d3812a20c --- /dev/null +++ b/blog/templatetags/blog_extras.py @@ -0,0 +1,19 @@ +from django import template +from django.contrib.auth import get_user_model + +register = template.Library() + + +user_model = get_user_model() + +@register.filter +def author_details(author): + if not isinstance(author, user_model): + # return empty string as safe default + return "" + if author.first_name and author.last_name: + name = f"{author.first_name} {author.last_name}" + else: + name = f"{author.username}" + return name + diff --git a/blog/tests.py b/blog/tests.py deleted file mode 100644 index 7ce503c2dd..0000000000 --- a/blog/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/blog/views.py b/blog/views.py index 9a5bffd5b2..9218b97915 100644 --- a/blog/views.py +++ b/blog/views.py @@ -1,5 +1,8 @@ +from django.utils import timezone +from blog.models import Post from django.shortcuts import render # Create your views here. def index(request): - return render(request, "blog/index.html") + posts = Post.objects.filter(published_at__lte=timezone.now()) + return render(request, "blog/index.html", {"posts": posts}) diff --git a/templates/blog/index.html b/templates/blog/index.html index 47997275b7..e101df7b65 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -1,5 +1,20 @@ {% extends "base.html" %} +{% load blog_extras %} {% block content %} -

Index Template

+

Blog Posts

+ + +{% for post in posts %} +
+
+

{{ post.title }}

+ By {{ post.author|author_details }} on {{post.published_at|date:"M, d Y" }} +

{{ post.summary }}

+

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

+
+
+{% endfor %} + + {% endblock %} \ No newline at end of file From 708423c63934d8cf759286974e994125d141f3a7 Mon Sep 17 00:00:00 2001 From: distance Date: Fri, 26 Apr 2024 13:06:12 +0200 Subject: [PATCH 09/46] custom_Filters --- blog/templatetags/blog_extras.py | 25 ++++++++++++-- blog/templatetags/blog_extras_intial.py | 46 +++++++++++++++++++++++++ templates/blog/index.html | 2 +- 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 blog/templatetags/blog_extras_intial.py diff --git a/blog/templatetags/blog_extras.py b/blog/templatetags/blog_extras.py index 8d3812a20c..c48fd3da97 100644 --- a/blog/templatetags/blog_extras.py +++ b/blog/templatetags/blog_extras.py @@ -1,19 +1,40 @@ +from django.utils.html import format_html from django import template from django.contrib.auth import get_user_model + register = template.Library() user_model = get_user_model() @register.filter -def author_details(author): +#def author_details(author, current_user=None): +def author_details(author, current_user): if not isinstance(author, user_model): # return empty string as safe default return "" + + 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}" - return name + + + if author.email: + prefix = format_html('', author.email) + suffix = format_html("") + + else: + prefix = "" + suffix = "" + + + return format_html('{}{}{}', prefix, name, suffix) + + diff --git a/blog/templatetags/blog_extras_intial.py b/blog/templatetags/blog_extras_intial.py new file mode 100644 index 0000000000..5a27f604b9 --- /dev/null +++ b/blog/templatetags/blog_extras_intial.py @@ -0,0 +1,46 @@ +#remove these + #used in 2nd Phase: from django.utils.html import escape + #used in 2nd Phase: from django.utils.safestring import mark_safe + +from django.utils.html import format_html +from django import template +from django.contrib.auth import get_user_model + + +register = template.Library() + + +user_model = get_user_model() + +@register.filter +def author_details(author): + if not isinstance(author, user_model): + # return empty string as safe default + return "" + if author.first_name and author.last_name: + name = f"{author.first_name} {author.last_name}" + # 2nd phase with escape and safe: + #name = escape(f"{author.first_name} {author.last_name}") + else: + name = f"{author.username}" + # 2nd phase with escape and safe: + # name = escape(f"{author.username}") + + if author.email: + email = author.email + prefix = f'' + suffix = "" + + else: + prefix = "" + suffix = "" + + + return format_html('{}{}{}', prefix, name, suffix) + + # 2nd phase with escape and safe: + # return mark_safe(f"{prefix}{name}{suffix}") + #return f"{prefix}{name}{suffix}" + + + diff --git a/templates/blog/index.html b/templates/blog/index.html index e101df7b65..ac127bcea6 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -9,7 +9,7 @@

Blog Posts

{{ post.title }}

- By {{ post.author|author_details }} on {{post.published_at|date:"M, d Y" }} + By {{ post.author|author_details:request.user }} on {{post.published_at|date:"M, d Y" }}

{{ post.summary }}

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

From c595c20130779c7ba8770c614a3cf1b57bca87ad Mon Sep 17 00:00:00 2001 From: distance Date: Fri, 26 Apr 2024 16:28:31 +0200 Subject: [PATCH 10/46] Finish custom template tags --- blango/urls.py | 4 +- blog/templatetags/blog_extras.py | 75 ++++++++++++++++++++++---------- blog/views.py | 7 ++- templates/blog/index.html | 25 +++++------ templates/blog/post-byline.html | 2 + templates/blog/post-detail.html | 25 +++++++++++ templates/blog/post-list.html | 6 +++ 7 files changed, 107 insertions(+), 37 deletions(-) create mode 100644 templates/blog/post-byline.html create mode 100644 templates/blog/post-detail.html create mode 100644 templates/blog/post-list.html diff --git a/blango/urls.py b/blango/urls.py index 16719906b3..806f4d52e0 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -20,5 +20,7 @@ urlpatterns = [ path('admin/', admin.site.urls), - path("", blog.views.index) + path("", blog.views.index), + path("post//", blog.views.post_detail, name="blogpost-detail"), + ] diff --git a/blog/templatetags/blog_extras.py b/blog/templatetags/blog_extras.py index c48fd3da97..aa322ed9a0 100644 --- a/blog/templatetags/blog_extras.py +++ b/blog/templatetags/blog_extras.py @@ -1,6 +1,7 @@ from django.utils.html import format_html from django import template from django.contrib.auth import get_user_model +from blog.models import Post register = template.Library() @@ -8,33 +9,63 @@ user_model = get_user_model() -@register.filter -#def author_details(author, current_user=None): -def author_details(author, current_user): - if not isinstance(author, user_model): - # return empty string as safe default - return "" - - if author == current_user: - return format_html("me") +@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.first_name and author.last_name: - name = f"{author.first_name} {author.last_name}" +if author == current_user: + return format_html("me") - else: - name = f"{author.username}" +if author.first_name and author.last_name: + name = f"{author.first_name} {author.last_name}" - - if author.email: - prefix = format_html('', author.email) - suffix = format_html("") +else: + name = f"{author.username}" - else: - prefix = "" - suffix = "" - +if author.email: + prefix = format_html('', author.email) + suffix = format_html("") + +else: + prefix = "" + suffix = "" - return format_html('{}{}{}', prefix, name, suffix) + return format_html("{}{}{}", prefix, name, suffix) + + + +@register.simple_tag +def row(): + return format_html('
') + + +@register.simple_tag +def endrow(): + return format_html("
") + +@register.simple_tag +def row(extra_classes=""): + return format_html('
', extra_classes) + +@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] + return {"title": "Recent Posts", "posts": posts} + + + + diff --git a/blog/views.py b/blog/views.py index 9218b97915..339e5f28d1 100644 --- a/blog/views.py +++ b/blog/views.py @@ -1,8 +1,13 @@ from django.utils import timezone from blog.models import Post -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 # Create your views here. def index(request): posts = Post.objects.filter(published_at__lte=timezone.now()) return render(request, "blog/index.html", {"posts": posts}) + + +def post_detail(request, slug): + post = get_object_or_404(Post, slug=slug) + return render(request, "blog/post-detail.html", {"post": post}) \ No newline at end of file diff --git a/templates/blog/index.html b/templates/blog/index.html index ac127bcea6..1a4bd3452f 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -1,20 +1,19 @@ {% extends "base.html" %} {% load blog_extras %} - {% block content %} -

Blog Posts

- +

Blog Posts

{% for post in posts %} -
-
-

{{ post.title }}

- By {{ post.author|author_details:request.user }} on {{post.published_at|date:"M, d Y" }} -

{{ post.summary }}

-

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

-
-
-{% endfor %} +{% row "border-bottom" %} +
+

{{ post.title }}

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

{{ post.summary }}

+

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

+
+{% endrow %} -{% endblock %} \ No newline at end of file +{% endfor %} +{% endblock %} diff --git a/templates/blog/post-byline.html b/templates/blog/post-byline.html new file mode 100644 index 0000000000..c440349856 --- /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-detail.html b/templates/blog/post-detail.html new file mode 100644 index 0000000000..96642ca32f --- /dev/null +++ b/templates/blog/post-detail.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% load blog_extras %} +{% block content %} +

{{ post.title }}

+ +{% row %} +
+ {% include "blog/post-byline.html" %} +
+{% endrow %} + +{% row %} +
{{ post.content|safe }} +
+{% endrow %} + + +{% 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 @@ +

{{ title }}

+
    + {% for post in posts %} +
  • Read More
  • + {% endfor %} +
\ No newline at end of file From f394b84675918ddfecf7a5a641b54d0069c12a19 Mon Sep 17 00:00:00 2001 From: distance Date: Sat, 27 Apr 2024 11:14:04 +0200 Subject: [PATCH 11/46] Finish custom filters --- blango/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 163 bytes blango/__pycache__/settings.cpython-311.pyc | Bin 0 -> 2680 bytes blango/__pycache__/urls.cpython-311.pyc | Bin 0 -> 1272 bytes blango/__pycache__/wsgi.cpython-311.pyc | Bin 0 -> 683 bytes blango/settings.py | 18 +++---- blango/urls.py | 2 +- blog/__pycache__/__init__.cpython-311.pyc | Bin 154 -> 161 bytes blog/__pycache__/admin.cpython-311.pyc | Bin 806 -> 911 bytes blog/__pycache__/apps.cpython-311.pyc | Bin 521 -> 528 bytes blog/__pycache__/forms.cpython-311.pyc | Bin 0 -> 786 bytes blog/__pycache__/models.cpython-311.pyc | Bin 1894 -> 2943 bytes blog/__pycache__/views.cpython-311.pyc | Bin 0 -> 1828 bytes blog/admin.py | 6 +-- blog/forms.py | 8 +++ blog/migrations/0002_comment.py | 29 +++++++++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 2168 -> 2175 bytes .../__pycache__/0002_comment.cpython-311.pyc | Bin 0 -> 1816 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 165 -> 172 bytes blog/models.py | 17 +++++- .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 174 bytes .../__pycache__/blog_extras.cpython-311.pyc | Bin 0 -> 3001 bytes blog/templatetags/blog_extras.py | 49 +++++++++--------- blog/templatetags/blog_extras_intial.py | 46 ---------------- blog/views.py | 25 ++++++++- db.sqlite3 | Bin 163840 -> 180224 bytes templates/blog/index.html | 1 + templates/blog/post-comments.html | 39 ++++++++++++++ templates/blog/post-detail.html | 4 ++ 28 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 blango/__pycache__/__init__.cpython-311.pyc create mode 100644 blango/__pycache__/settings.cpython-311.pyc create mode 100644 blango/__pycache__/urls.cpython-311.pyc create mode 100644 blango/__pycache__/wsgi.cpython-311.pyc create mode 100644 blog/__pycache__/forms.cpython-311.pyc create mode 100644 blog/__pycache__/views.cpython-311.pyc create mode 100644 blog/forms.py create mode 100644 blog/migrations/0002_comment.py create mode 100644 blog/migrations/__pycache__/0002_comment.cpython-311.pyc create mode 100644 blog/templatetags/__pycache__/__init__.cpython-311.pyc create mode 100644 blog/templatetags/__pycache__/blog_extras.cpython-311.pyc delete mode 100644 blog/templatetags/blog_extras_intial.py create mode 100644 templates/blog/post-comments.html diff --git a/blango/__pycache__/__init__.cpython-311.pyc b/blango/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..271e86f76c78d2d7910ee764702e03016d0fdc5a GIT binary patch literal 163 zcmZ3^%ge<81ck}kX(0MBh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%gos-CbT%U zs5r*gHO3{sJTE6dF{L;rDJL;6Js*vVkI&4@EQycTE2#X%VUwGmQks)$SHuc524qn& TKalvq%*e?2fdNJoF$2W_H5(>T literal 0 HcmV?d00001 diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f9e02a476e0d0dcc867dd2d862b1f40fa8b2595 GIT binary patch literal 2680 zcmb7G&2JM&6rc6?cbs5|!-wPuQE)=m1X8|)Qa1J`SU7f?bx6C4SZzELXTy5e?5qjd zlaH;`1GjSEP;sED+CL^#NR9S{)KhOOapTmtYdf(FQt4**&CHwM`<(a2`8Ao0A$aco zadl@gjnLojV(^6elMjeN=x2nHh%keFD%0|bKE|EA>t!}H-PyazTWdoa4;Jl z-@xMcW5(V;|x9xs0=>i(MgYH6{Z;w$MKXnfzOJk zz;*_{)9{_a=fp{TUd&>@fbtjQSqLvy+LN`Wo}1(%d&NgQ+Ei71fW*+Z7emd7#(5CmUT-;al&+o{WqVB}_P+ zhI=H`4s5G>6TF-2V_FKvD%qC}!g>KK<^e0Xk;7#5c+XlGRoQof9g-J%ydkOlr$*q+ zTe`*;z2QHg)YxH*KprV%e+YVO#1|P-IP3=pYsj9`@Jez>a5RWP!iI%cdr2IUy7`$P zRHgA`Pi{PcwqUg8HhuqOMd+ow&1mD_Dd zw|ACy7?T~jqngJhj<-)TNuR2UNp4s%VD{&EpB>_>E9EM02ddmEZ=|5vtp_81dj-^% zDc#o%EWw4MV9y-8L5abcZufRfA}B3ImEFF0)I;cowfLo)RuxTYby^joHO<`<`ER~d zzSl2neIHL0-2YMq)lQ2TO5;-vL88rdsO{gV7p+JE?tN&Sf_0&<7gE{MF;OYIq%@(i z^ks#pI6q~FB?)eHNwULjxHna0+wenbZJ$oSipb^5$xD%xm2p~o7_5Y zC)R7Vy0o!gDb}jXcC^l~u2r}?FWBi#VWsTiE9D~R7Ly$LSEY z2OCA#4|}4-FLN7}x&$_A(sG%vlzu}W+~1auH;Z?-+)AQbtNd0;-`7-K#&oM!<0Hf1 z2as(ae2m}Xv~5d_n#KJ1Jv;|4CDA>Ou#`d$s%Uz5M_toBNXj=e@+M`(myP` zi2S(t)8b$8=|AJsZ{ru=x$n%o_BayUO?HFt$1^X2-J}zXO!yo$Al;Oc ziKJXcp8$R$0HzZteW9ChA}GT%N8?j3AHE&Ge3-s+l*ztNdm>MGfa31D-|EvBR|J_%GP4PwiScgqeSEAN=@AGx1RhYB@sKX0yI9dfs1@l44X6igFQJfbGLvM(?#?o^u^*>i zgdRja6!hS^BK6?WKO==4%AN#I-U7Y#ZG@J^>7B04rE^6j!aOiqun1%7rx;>Cj2nGAbYtr-|B4VnH{Tq&rXn zI21dU$TLGS-HF7~jgZLGKRmN%Jo8Bm&V8Ra5V#M;j-{QdO=9>O=$b`Ev4&%LZHm@j zJx2@FYuW7{vjUrXnzq^@Xrsp*-wFc4JidV(n+J%|UO>1E;3%NzjZHSWftD#lE@cGS zUW+m}N`ZWbSezhENJT?JIziy`ifLlniMecUp^8Jx<%zt*|A5j(r z#u$oFLAEnpDwRjPI@Bd)LXY)2cUcAVn|iF^)$h39q*Vi5SJ*E{S6v21JKabh`%cr6GxA!f+@IUv#~Jr7+;3!o85Q zM_4iD4785bmgi9!D3=a1QFBd!j65d1r|zaVkYg=9%3>~$A||vqR{hX(erGprmQnhk z&$OTA{2MaT)#|J5W_{Oh)%Nxp&3en<*{$xst{vaaFYhX*btw9(Tuh6;Zxr}`YDtye z%XPSck?&@xiEVh&A`_!9ks@`>LCSjMkn42m2H{t@%miI+zS!#QE0(CvPOVdwW5Fe% zs*|(+7czJ^KDxT~EPvkTNy$K%e*@~RaWfksG_#7RFoVq92jraG&e{|/", blog.views.post_detail, name="blogpost-detail"), + path("post//", blog.views.post_detail, name="blog-post-detail"), ] diff --git a/blog/__pycache__/__init__.cpython-311.pyc b/blog/__pycache__/__init__.cpython-311.pyc index 60abe4f9f8c77b77407d238acb8b96254ee715da..b4f9abca7f3fa5bdec9b627252f9f7eac6ecbe6d 100644 GIT binary patch delta 27 hcmbQmxR8;1IWI340}vQgXiwxeWi*-SC_gdM5dco{1_=NF delta 27 hcmZ3;IE#^cIWI340}$Nbt~HU{lu>u0qx{5lD*#)Z2KE2| diff --git a/blog/__pycache__/admin.cpython-311.pyc b/blog/__pycache__/admin.cpython-311.pyc index 19bb88b664078a382b5432ae71442c0c20ecb503..a353f6620a404e45e8626b62bf7678af59f255d7 100644 GIT binary patch delta 400 zcmZ3+*3ZtnoR^o20SK;d)=7(<$ScYCYNEQlRW553DY zj1$kvi!k3}4oOVE#S)NTTyl%uIX^cyHE-gN5Jt|)zKqF?hLblk%F8C^rRT>a<>aTw zB&OtM=IIqw7J+OA6C9JDF{<+j!MJ{!T$3f4bi9f{9=XNko1c=J;|S6OR(y-4II|@6 z7DrKPdS-D+YEh9ONFP|UFp%|&!zMRBr8Fniu1FHd1-g-;c*Eo_CTV#t21e}*3|hF* oI8&!mxA(bVSZ5b0#BhzFyMtN?gTg)Me>9<${@{1>1 zGKMg6OzvY$7SnRJiU}=FEh>)jb&YY!FVD-#PfRJE%*Q0g1v0M)M6gfRV^Zf4gmL{e zIVUGF>G0j+^36|4&2dc0&CDxepS*xcBwPTb2CPsB$oj=$lbfGXnv-f*Bmv|y0&(#a tAn}2jk&*ERgTe(=^nrtcQRxDMA|{$F$gIaGFxi1wkNX0H3J`(q007&`M^XR) diff --git a/blog/__pycache__/apps.cpython-311.pyc b/blog/__pycache__/apps.cpython-311.pyc index 681ebbdde7a4f848de584bbd265f7196833ee750..b452d18c5c56874e37f72b1437fe371c863bc205 100644 GIT binary patch delta 30 kcmeBVnZUxmoR^o20SF3{wKsC}GBO%WmSmKlY{+;609)+_TL1t6 delta 30 kcmbQh(#gWToR^o20SNAI*V@R<%gCrXS&~tHvOVKP0BBwZ8~^|S diff --git a/blog/__pycache__/forms.cpython-311.pyc b/blog/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fadbb33c95c050fc24ffad1ec59a8dfd24f9362c GIT binary patch literal 786 zcmZuvF>ezw6n?hvuAyl~+5svm#Dqj76vT?EE-f&n3t~B)WL;|~qVk==PKDSQ7&>-9 z>OUv~R(?pwlUF9TROyt7_wEux)IOg-fA+IKKfib1*4I}7t@`zJ@B_!+G?`mGBa?k3 z=b%6_0|iSNBhC#kL@F3?cnwN?0TpR>9L*132!Nm18%tvqzkzP@ho)T?;(>LAN3ybK zi$a&9quIuKn2SuF;Pf0cq)b7|>x&p(#4k9CV{a@KN}zZY+B30ND83}fW;-WaNEU)x zq>*AnhS6~p5Yx_o0=>Q-mLrVspqqr2lx1FMDZ{Fig;ghp_?na-PIA+D9EBcY zbZQfl!8>h~{|*zne)@dxMfTQf=d=FHY~Ow?jm?$M4ozMT?P9RQtWHkv{ihI746cQz z6YTy0!smaj>fAq#tIh4R?bC#s>!90hA{=xhM`d+wg0=awyGyLzwLy$DYoJ2=9fXDW znDhAI7;eH9tzF;ZXoN|Va+RoKdX}RgE>({q9>lge59ptaS literal 0 HcmV?d00001 diff --git a/blog/__pycache__/models.cpython-311.pyc b/blog/__pycache__/models.cpython-311.pyc index 891462dfc66ff436810ca1e0d96ee8e791376deb..94e62204114d747133252f5983184527b4cc6b33 100644 GIT binary patch literal 2943 zcmcIm%}*Og6rZ)%A9&X`#*j|}q)9+1#Hy->q6i^Hd?YQwNwL#|i?mvp!CAWY(%E$q z(yH|#s(j$UExDBwO&SgybLb(5`~kMK2WxLtPq`7nr8)JzU7HU>X)Yb_zMYx(vClid z-<$a@8jTQWfB!zT_)CD0zi`lOFm~I{COn=IN~oZdf{+sg?E7?I!JqTHeZMXi0=YmT zmhO~yn@?nxrl2O0;})|R+4Mg23C<1tYj)> z$e{cVLk|j0uwYQ8o6rlJ%(Ap%-W>Ef@ms9OxVA9Mbk)*~qLXM$Oc|VM`QkKN0S{^1 zC|ay&WmlHjLL*oA(ad~j7>3UXBRPSRoXd*_e^nl;$wRh0RG!=xg}}LO za#VOhG2WpS6JY||jtt-D-&TwTmhk;c?%Z^@wD^-46osJIzu!CJ==i(rdIl7;|>VJ9?h6bb-y zfKZ!JRDdqbVvfAvfnTuz{h*j%Vhfg{(JYwBYRNK`qVY&kDTQ&kIZh9D@>7r$KaJuH z3Ksy8eO_@~3MYQ^e)hI-wU#F~kFDLc^#)nZw#z+6Ko;>KrGQ7uOT&}xgCJK*Oq z!agLsafl)YxA`lG1CWgMRu=78?|Rto8+kMJHeHQnYO#zR%am``V?C(rsm1#2SRag& zm)C~vpyM%Q0p4E zyT;4udg993DLZjx>+@=2q?Q=56C>rX>K*-S{dPzHmRRk$UhBAScU&(|>q z#s2~M)VJwxL6wL8J#K)7*kS>792jxH0&D^;_-KfTdYF*}K`v@vIXB@;?B+4x#EA ziWKVQ<0@&y7A^G^`lenAlGq`P|fs*}Fz;j2!*t{!*Q$&KpaYg-frI5Zp; vmHySC_c*`1uWSzq!`tMjs4TAP?{R*2U)dSOxJN~0cy;tW&hH+hbjkJ);s2&C delta 611 zcmZuuzfTlF7~NU+*ByI%fO01!L_7;v1u9}e2#GOjBsm~(g)uRk#TnpWnI$uGF;G(w zweT{D|AMiyw(u{ovNO{W5)u;&Nvtf;@7wbVqf@+m-<$d7y?OJib{<7JON#%SPQx^?WjEA`ST>D6s`38*f#EFmhwI0@J^Nq zAN2zL!e4FP2|jOwS( zOm%I2wRQhq%Xpx^lM8+7OpluIptG5^zi@Kr)#3&6KmM~>Wb+#d-j+K6 diff --git a/blog/__pycache__/views.cpython-311.pyc b/blog/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2fd87313a3b16423f2c59a4f929783ba102013b GIT binary patch literal 1828 zcmZux-ESL35a0WDa_5f(1%n-w(3BJ_iJ>7NifBUBkEc*T34O349ggpkoVky%w~isY zNO|Z(g!*7mL;w{j4^?QDK7dq2|BQ2FDb`7mD)GddQK*pm#OyhC?IxqW*`3+h+1cOB z+^^ASF9Q1S&+#RXN9Zpqw1_=f9N&V)4$_dun8;#s3`5tf$y!{Fv-lhz%A6@!VotQA zoD|AD$dOzmTnlE8)tl>uwRjulWK9CDXpuRT>G_*pl3{`fF)V%EvGoia@Si#^38brz zW$88ng@|=q({a$ZtP|B)Eb0ZAusZej6g`P`&A_07wS02shJ&pFa}qzDMEE%Pj=u%A zg9z{z729^Ztrxep!kI|V#UfzTq&=ASTLOB>?17j4wx0Dn$GfD2E6*~>D561cB59!41SE z1I#g|J_F8n%9y?sAd|5ERY`XVRzP;?!-j{SUA+3i!so7z-Gz^@FI;oVw&~Y_ zyx-PsupTP5rD7XWybbzxk~H?q&<3w}r=v(g3h9NkaUQqZk?xiiLfz@cpMf2P;}mJ! zL;eMyyvKUH+S4-aj2rY}`_Vm*KigyX%?u*RmM1uaiZs0;b>BLN?7q%7cYE33Y*s@X z97(rMQ(u|@k#d@NmpxUc5-n5SHA~CToS)3i-vCi8Kz|x!eIP7VTAmY{PV$Bs7A$RJ zstXiq25qb^K~H94n8MpEV!i~W81xvfnlBJzO%Fs%Co7JIDOUJpU_k^n*Yc*J!R2|d zh!q^0P-o5h52OOt^TffJCL`!s&65??u;C_};EaG1)YAZEuTb_XFgVi;$Q3{uriA;b zUZ&xy%_ray&_v;LtH7Y%<-uR(@AJMqQI#iZ^2Fw~CsORTSCd|Apr|ldm;3Kb|9I;_ z9`WUoqx9%*`JPuzPu0>>TeDB%>BIQ>gZTMse6$uHZ6G1~PCYexm>N4sjs5nnpBk&C zF4a<(DpEZ&QITqi(fYZ}z43~)9j_;nJIeRUcd_kQC3ch?+Lda<7puw1T5{4)Og3cj z^fZPBFZ^`*?&YmnxGI_6Ql3VT(ofrO^6|Vsr}|T0!1xC-3i~f*4a5j6ehFT6GZPII ztw>uxTOx+(Hh&^AO=s3}G#xawlQ6|8NSs)@D-I@w5^+O_o7oJ(?+wqIIXCPR_rAyP-o5wk zevL*$476|lSZMt0XPAHNiPr*S;q(Crzc7eFY@4aD@a5WE$5-*Ow$9h~cLJ3_hp+G~ z!!frRoIXz)*pRnUkg+najI0pM#um`un`n)F}fbAi4w>5p0pkpg|8stNM z(1mHeNg!Jl%?UoGo!8#V68?C!yF;{D1?mp}P%MocX_OXXAXHw;4RT@a9@g zwsHX_Apf&eT=#MfiTBsqT9ZZ=7Z?)44SEsKB}vB}t%s#C zBH56n${kFDMXl2TW6CLLvrM_WrJSEeN=K9jA*#4D;-(imA=KhR5Y*-%P3DU zjr6cy(+rPj%uPkD*1~mDQVJ?B#FR5N8gy3EbUQH4-guyIBjbK9K?TX#t|ZZrB*Dr%Z47cmlD^xK z+qSVJ>2|@=gwdC~UAYE0!d!Bv=EifLWNsWID^na~iX>CCVx?g`ZDp69zeBQ1#}7{)k=1pw`jD({ zTlMd)?A{=|N3wfX><7nztF{AIkNFdkEZ-r^Uywz_pD@pknEFW--YaOZa?}pFKju1zUO8xk7{}MX3l%*(?0++8Xn_N*$ubM#K|8{ zMLMPAW@6Fib;y@MdL`U)3;7fMBbeG(wPC_A^06!n*>P{wCAAY(d13PUi1CZpd)OlWax zQE`l~Ym7^Nd0tL_VoGsLQchxCdOm{6Pmd`{%`M1DEJ-a%OfQa!kI&4@EQycTE2#X% jVUwGmQks)$SHuc55oBjEKalvq%*e?2fdNJoF$2W_)JiLo literal 0 HcmV?d00001 diff --git a/blog/templatetags/__pycache__/blog_extras.cpython-311.pyc b/blog/templatetags/__pycache__/blog_extras.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6c6c52509a90df0687596ce8f7af2f82f5ab6b0 GIT binary patch literal 3001 zcmbsrO>Yxd@V)igyY{a0>5vi<>I6tJP8>wiiXz7spzVRu3ffB))pER>;IQ_lZ`Tll zE%}g2sLCOSN+8inIaFy-_!B*GT}LZnt%MY*>Vcb4IC1LCI(EDcJ(NE0o1LARH#2W$ z-aP*i4u=Sg7k^GHzV#FG7k)H{?+`ionG*7tC`6$e$p3{h1nR2uueKw zht<$>YqcUC<9B)Z!DIYx55HHD)@g?YuZCVlJ|@xQ(GfmoqZfAA*W5$IG9>HM6b2g7N@NO#eaBO??Sce-b_$qfAS5x%olL9B{cd zuCL@Ppy-iB|atJvlHYt~ ze?bIVOaD_+_QN;s!yW9L$F_&J4c_g#9-?SD;F0Voy0TE<6`>q}DR|_>QlR873ESZF zZp&{-R`8}HF!jfx3c=@BAO&mvNyB7%VJVeYlT(N2I~rY!Bs0-FOkJEE%V%<$sb6_m z9!st9$*D}rA!~t$W&FhihsU%!flSf7qceuKoX}#98D_eW$*WFK%QR6DC0(LlMV&Fx z5e~D>BwQzFi;O`vxRt{2v;)h-ZBi6u$ zYA|jG;}#bOj?E9$iH|$|WZWLQ;9@no&z485;3asr0(SIb1Ff8`oZXlEHomcfBY^Rt z^&52}a9yvY6V~8ljT6@=-uS58^OgXpjQ=g2*t}PjPTSJy%6MhG9w2g8WxOVZfhG2D ze)VK=OWFCNDo)$tv_+>6U@IhxpgFM8-4hlCpU4T?&ex=Qpw$mW+A)A z>=@JUkEIx{e@56ayx0%`#|L!wF<>#6MlXO00Z*;L3sos@OL2>jyV4)-$usaIkemWn z8s^z)Fw*8hKrJZf#2AnhFb}YvTqibHt5VdKq81-*$aew9({!+u-H_no&XAOd_n5-b z0#_m(L5bp-X~YHkn_FjTH)N_|V1%P?oZJw(;1bXd`T-oqX>zArm1b>e*5YRyk!s5e z`M(Zkb?wMOb`H$8Ipz3(%`kDF!DOExz|4MluGlE*z=*ytOK2?)yer5*g8;3q_2UVh z!bUZz$QP1JE|qk;KR?jp#+~9)_5%9^eW5P786Ainw%TbM&AxJ=nS{z z^dAaghi18j1hz(o7nmbqP7>}#Zn(Q+*oqzDMs9&+*aJso>JsD%ZiUd%mG6X%9Q++n zTRpW=^?0`2Dsz+XgmVRmj*-&~t{m>SqgdXFNw8eh)D)WqE}jSD0l>PCQd%Q1%lp*G zDa-rRNW$_yHFC~+--BIr_|ausI#rcMu;q(D=^B3YLp3~Nhes-*eHwg8BYQNmHDl38 zm7cfh`8qd9XX*sN8z13fEy`uWg=`wBbCgch2>@gdv?$^cdlcED?d(yo+ghDMb_2A_ nr2ebSznOVS2lwcp_0eRNCTyCha~%D&P5_{_DG$XZyT$bnhWdn~ literal 0 HcmV?d00001 diff --git a/blog/templatetags/blog_extras.py b/blog/templatetags/blog_extras.py index aa322ed9a0..f0884aa5f3 100644 --- a/blog/templatetags/blog_extras.py +++ b/blog/templatetags/blog_extras.py @@ -1,7 +1,7 @@ from django.utils.html import format_html from django import template from django.contrib.auth import get_user_model -from blog.models import Post +from blog.models import Post, Comment register = template.Library() @@ -11,29 +11,30 @@ @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) + 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/templatetags/blog_extras_intial.py b/blog/templatetags/blog_extras_intial.py deleted file mode 100644 index 5a27f604b9..0000000000 --- a/blog/templatetags/blog_extras_intial.py +++ /dev/null @@ -1,46 +0,0 @@ -#remove these - #used in 2nd Phase: from django.utils.html import escape - #used in 2nd Phase: from django.utils.safestring import mark_safe - -from django.utils.html import format_html -from django import template -from django.contrib.auth import get_user_model - - -register = template.Library() - - -user_model = get_user_model() - -@register.filter -def author_details(author): - if not isinstance(author, user_model): - # return empty string as safe default - return "" - if author.first_name and author.last_name: - name = f"{author.first_name} {author.last_name}" - # 2nd phase with escape and safe: - #name = escape(f"{author.first_name} {author.last_name}") - else: - name = f"{author.username}" - # 2nd phase with escape and safe: - # name = escape(f"{author.username}") - - if author.email: - email = author.email - prefix = f'' - suffix = "" - - else: - prefix = "" - suffix = "" - - - return format_html('{}{}{}', prefix, name, suffix) - - # 2nd phase with escape and safe: - # return mark_safe(f"{prefix}{name}{suffix}") - #return f"{prefix}{name}{suffix}" - - - diff --git a/blog/views.py b/blog/views.py index 339e5f28d1..c0a4a9fd62 100644 --- a/blog/views.py +++ b/blog/views.py @@ -1,6 +1,8 @@ from django.utils import timezone -from blog.models import Post +from blog.models import Post, Comment from django.shortcuts import render, get_object_or_404 +from django.shortcuts import redirect +from blog.forms import CommentForm # Create your views here. def index(request): @@ -10,4 +12,23 @@ def index(request): def post_detail(request, slug): post = get_object_or_404(Post, slug=slug) - return render(request, "blog/post-detail.html", {"post": post}) \ No newline at end of file + + 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}) + + return render(request, "blog/post-detail.html", {"post": post, "comment_form": comment_form} +) \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 1adfc0eac7a6f354004f72ef20dd75223d2f6fb9..14e3bde70a9b35ba060f67cc4cdbfec4eb06b356 100644 GIT binary patch delta 2426 zcma)8Yi!$86!x_pJ9e9#v@~l=5p~ma5Vp3Coi=qS?Xpr{dvt?J69|^NjoYMa* z_e5klB6oH4$ic2qccHmyg>4WaLcwJifp_6B9D|qOE%+98L!{6m-3(hjZl( zQ^t#!P86TiQxj%vIysTiEo_jj6ERqT7zL|v8dG^6Vg--92WmX5x5>6HB_`z3{m(Ie zaWTj5h&rq8tMZ9g#YMapN5xs6-$9|acHwZ+I8tUlpr<48`za zrT?uSs%23d+CbP}$D#n*#ILoe+(^b(!=hG1B9{ZV6kd5`BE$HhJ;#3;bwWRAph7zi zb=KYBU=R}gwrsVf&z3EP!KDvJ7&r6T0>1N~%MC5*4YS<#cK?=`)1E=6Ucom+3C&7e+g%(1PN1Ms*oK6tx)0Z_q~;%9 zLXE9|dR>NA)!pb?+926NB&UVoKqU$UU zbjCG1uQ63rG^#c>cFwm58=J2bM+qrf_ zo4uXszWD$x7RmQ#H*>dBS2QiGb#LNcIWxOqw+VGe)Y2;2Y~ZkmG|k#W8Z#6KL6#NDK9-76et{pbmnH1uG<1=kV(iXh9ORj1ZMix8d1Cqt zT!Jgu_&hfK0=C;gyW2{ZHg8*N6DP+E4N(eCKomD!7tBu4PU?^ljUr*V^eXl8<|d8l z=^1M#gUixPNmjy;ju@lTc*aawMk-!eGg?H(H52a8f~u-$*Pr2skdq49(GDbZOVXu- z6Bz?Pkk`;sX@{Gni+Bua-Cf~Ly;V8GlZ>i(KLzuyE7fPJN2@MXDdOv5vvU{!Ht)xW z2gmo$3%z{(o;Eb!o=nA!$0|?1*cgd#W@5>BtY=&chqW=Ka(;XM!YI07wzMH#3+|9u#|#BvOqDX?IDYC&kc% z2Xis3vQjAafa^DkbSyAv=*X(&PDDDO(W@<%my+ zy)X$nc!76#j7u;vgPIdIM`4+R*CpLwv^+!mMBt5nr+DNhm-N82hA^9~wAZhFY+e0VG3Nh;Sfrd84 zKFf`fv-II!FGq`D0j)-8hQ367CiKV8Zr`PGm7`6tiWU4sTLk<7t34m@^}--%<1L=! z5pLlsF2WG(N!Z3KdPC*GCc#r&!NFit1bh>0G(S>pXAeb+ASKp306V3zCtij{f?rs4 z!qu;c%Zw9+GEPNN+$~Xb#Fm8d7ucOa783@mB8^ZlAR>54`m1ADBIsgC5Z|EoaD`IF cI9C+q_B|F-T`PuVcaCKt`3`qJS!QngZ{*^t$p8QV diff --git a/templates/blog/index.html b/templates/blog/index.html index 1a4bd3452f..3a25e3e412 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -17,3 +17,4 @@

{{ post.title }}

{% endfor %} {% endblock %} + diff --git a/templates/blog/post-comments.html b/templates/blog/post-comments.html new file mode 100644 index 0000000000..89907db323 --- /dev/null +++ b/templates/blog/post-comments.html @@ -0,0 +1,39 @@ +{% load blog_extras %} +

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.as_p }} +

+ +

+
+{% endcol %} +{% endrow %} +{% endif %} \ No newline at end of file diff --git a/templates/blog/post-detail.html b/templates/blog/post-detail.html index 96642ca32f..d3f2072545 100644 --- a/templates/blog/post-detail.html +++ b/templates/blog/post-detail.html @@ -14,6 +14,10 @@

{{ post.title }}

{% endrow %} + + +{% include "blog/post-comments.html" %} + {% row %} {% col %} From 0653a5fdef301610d8beaf34df93cb22a3e58f07 Mon Sep 17 00:00:00 2001 From: distance Date: Sat, 27 Apr 2024 12:30:27 +0200 Subject: [PATCH 12/46] Finish crispy forms --- blango/__pycache__/settings.cpython-311.pyc | Bin 2680 -> 2800 bytes blango/settings.py | 6 +++ blog/__pycache__/forms.cpython-311.pyc | Bin 786 -> 1457 bytes blog/forms.py | 11 ++++- db.sqlite3 | Bin 180224 -> 180224 bytes templates/blog/post-comments.html | 46 +++++++++----------- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc index 8f9e02a476e0d0dcc867dd2d862b1f40fa8b2595..bdcc8e7539f84b6304298f7721119982e3f2c1aa 100644 GIT binary patch delta 240 zcmew%@=@w`{X@Lb2e8p|6*d~ z+g!rt%E+WCx%oJ|3gcu2PA@KPpjJj8E>4?V!kHn+A;k270Y)_NeGr-amh%NK3nR-1 L1|U(S2~-IHi`^SN diff --git a/blango/settings.py b/blango/settings.py index d900daad9d..decdb89b87 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -39,6 +39,8 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', + 'crispy_forms', + 'crispy_bootstrap5', ] MIDDLEWARE = [ @@ -131,6 +133,10 @@ 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 diff --git a/blog/__pycache__/forms.cpython-311.pyc b/blog/__pycache__/forms.cpython-311.pyc index fadbb33c95c050fc24ffad1ec59a8dfd24f9362c..4c1ed7435098159253396c73c62d4fd50f0c2045 100644 GIT binary patch literal 1457 zcma)6&1)M+6rb6T(MRGmAEFJleQ8J_CA}1U2yTim{R?`_T{eb_fu3^8O+h{cpZeaeR<`UOGCT9;_vUHe`|W#g z^vB3Zk-*s6`>ghg67m|AY`A^Us{nXT1QAq{kb0CNwxku>o}FM@I-%>i33en4b6$=T zi`*l^-5`PmbTOYf-L?tYft3!gAae7hoPUFAnHskisZax8^R0!j5d$jTg`=+nxg03a za<`%|44QGZ?;zQnzS5fk@H0>ih(`tStb_su3f8s-VPU1ivo}eN3VS2(Ih*7s>bXF| zF=zVDG2&1F$V@VNmti|{AK0EFXdPY`7C5jE&mVIzH5{Dz~GRri^@{ zD+|(Z)}x~>s#lUEPA#t*2RSILFfRQui1oqKIseh7+qpRUWOS3^qb`D3l|WRp?*&Yd z(Fd@4e}njyK8L!-$L#SSsf4VN;WRG}Y-_YZ)~pxwG4xKH14sR_4f{@}!23KaCE&#Y z-pV?0@Jj^o$dM}O$r5h^$DILB>`biCoMAd?EQQX?IpgXUG?v2l#QHRvjhJ(V@eKPR zX*yHzg#g2HD^6c>jH3ftGmfw7+OVaSWG`@DmA=-TYjiy^F|n>L0Z`a74@<`zBMWm5 zEc!bT@776&O+E^K`R2FME*sy+Ir*~GW;1_%2qK;Tmd2mne|CReY%FzHabxKpHoC_~ zf4{pm^YX#&*p2A!<(KLj zsF(`<2t?PWly=DF_VDbGPujz?Lq2T}&n_c$qD`(IpIw%xkdo8l$+c(K-{Sef{}>aR F{{gHFQCk22 delta 395 zcmdnUJ&BEPIWI340}$NUqLa3YVIrRdqsv5fSt}+6cZL+^7KRj-RK_e8kOB}`#>~L5 z8i*kviZz8bm_d{6C5WrZc#AbHzbLnO;yOiU4F-mZS5%o&m?v{Gb}+I`p2Qf;$^lfw zHu)8!vXcOiRU`x?{4`mLK$aJAg9sj&a1lR<1y&^pVzI#pRv@F;21racWHFxX&B9p^ zQ3X=qr^$AUEhQ^4FFn5q7aFEDrjF#>&HV`h~7z<^0iP@SUx5hVTv IizL_!0Ioq$l>h($ diff --git a/blog/forms.py b/blog/forms.py index 472b894170..96ec088576 100644 --- a/blog/forms.py +++ b/blog/forms.py @@ -1,8 +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"] \ No newline at end of file + 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/db.sqlite3 b/db.sqlite3 index 14e3bde70a9b35ba060f67cc4cdbfec4eb06b356..f05f31f02e52332bf29516e6927f592a7202a99e 100644 GIT binary patch delta 223 zcmZo@;BIK(o*>O=JWlWUgRnU}a=xWn`ddXklz>X^2~fJ%@>dA)C{2 yyGK9M9sw?9{`U<0zxluNzuzoa@Q{D{gL)QDGLalZ0Wz^*>n;HTgG?`nOfLbqOfLesPzVAKK>!c04?(jb zFf|XiL5~8%5Cj4b^#HdJpaIPfk)R-#;C})H69)nW0|5yK3IG5A0uT295Bv}Jvk`#n O4+#VV2>}8F0|^O%XdChX diff --git a/templates/blog/post-comments.html b/templates/blog/post-comments.html index 89907db323..17840deb3d 100644 --- a/templates/blog/post-comments.html +++ b/templates/blog/post-comments.html @@ -1,39 +1,33 @@ -{% load blog_extras %} +{% load blog_extras crispy_forms_tags %} +

Comments

-{% for comment in post.comments.all %} +{% 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 %} + {% col %} +
Posted by {{ comment.creator }} at {{ comment.created_at|date:"M, d Y h:i" }}
+ {% endcol %} {% endrow %} - - {% row "border-bottom" %} - {% col %} -

{{ comment.content }}

- {% endcol %} + {% col %} +

{{ comment.content }}

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

No comments.

- {% endcol %} - {% endrow %} + {% 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.as_p }} -

- -

-
-{% endcol %} + {% col %} +

Add Comment

+ + {% crispy comment_form %} + + {% endcol %} {% endrow %} {% endif %} \ No newline at end of file From 94c45dcffc00eda9fb3414b3762002dbce492dd4 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 5 May 2024 19:36:03 +0200 Subject: [PATCH 13/46] update --- blango/__pycache__/settings.cpython-311.pyc | Bin 2800 -> 2800 bytes blango/environ_test.py | 0 blango/settings.py | 1 + manage.py | 2 ++ templates/base.html | 5 +++++ 5 files changed, 8 insertions(+) create mode 100644 blango/environ_test.py diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc index bdcc8e7539f84b6304298f7721119982e3f2c1aa..29d720adc7d9ea514bfda39811a9c23e81f6e9c5 100644 GIT binary patch delta 29 jcmew$`azU;IWI340}z~wH%vRWk#{;LBgf{YoZgH8g%}8Z delta 29 jcmew$`azU;IWI340}zCq(Mdb9k#{;LBirVsoZgH8gRux} diff --git a/blango/environ_test.py b/blango/environ_test.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blango/settings.py b/blango/settings.py index decdb89b87..b0e0663578 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -13,6 +13,7 @@ from pathlib import Path import os + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/manage.py b/manage.py index c66b327f71..8fbae3230d 100644 --- a/manage.py +++ b/manage.py @@ -7,6 +7,8 @@ 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 except ImportError as exc: diff --git a/templates/base.html b/templates/base.html index 234177377b..849ed2c0e7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -8,6 +8,9 @@

Hello World

+ + + {% block content %} {% endblock %} @@ -16,6 +19,8 @@

Hello World

+ + \ No newline at end of file From 8d0308ac37e049ec3d5d424c94fd0336f7e62046 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 5 May 2024 19:37:33 +0200 Subject: [PATCH 14/46] environmentTest --- blango/environ_test.py | 16 ++++++++++++++++ blango/wsgi.py | 7 +++++-- manage.py | 6 ++++-- templates/blog/index.html | 1 + 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/blango/environ_test.py b/blango/environ_test.py index e69de29bb2..bc2aacb2b3 100644 --- a/blango/environ_test.py +++ 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/wsgi.py b/blango/wsgi.py index 83565cf12c..387004ddc3 100644 --- a/blango/wsgi.py +++ b/blango/wsgi.py @@ -7,10 +7,13 @@ https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ """ + import os -from django.core.wsgi import get_wsgi_application +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blango.settings") +os.environ.setdefault("DJANGO_CONFIGURATION", "Prod") #--set for config -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blango.settings') +from configurations.wsgi import get_wsgi_application +#from django.core.wsgi import get_wsgi_application application = get_wsgi_application() diff --git a/manage.py b/manage.py index 8fbae3230d..bb13551cdd 100644 --- a/manage.py +++ b/manage.py @@ -7,10 +7,12 @@ def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blango.settings') - os.environ.setdefault("DJANGO_CONFIGURATION", "Dev") + os.environ.setdefault("DJANGO_CONFIGURATION", "Dev") #--added to read configurations try: - from django.core.management import execute_from_command_line + #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 " diff --git a/templates/blog/index.html b/templates/blog/index.html index 3a25e3e412..78294def4f 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -3,6 +3,7 @@ {% block content %}

Blog Posts

+ {% for post in posts %} {% row "border-bottom" %} From 3b978e869d4c953768030a88eedfdb4c18482ec8 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 5 May 2024 20:50:28 +0200 Subject: [PATCH 15/46] Finish database optimization --- blango/settings.py | 4 ++++ blango/urls.py | 10 ++++++++-- blog/models.py | 6 +++--- blog/views.py | 14 +++++++++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/blango/settings.py b/blango/settings.py index b0e0663578..618cb250b6 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -42,8 +42,11 @@ 'blog', 'crispy_forms', 'crispy_bootstrap5', + 'debug_toolbar' ] +#INTERNAL_IPS = ["192.168.10.93"] + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -52,6 +55,7 @@ '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 diff --git a/blango/urls.py b/blango/urls.py index e03e66d0ed..163d4076c0 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -15,12 +15,18 @@ """ from django.contrib import admin from django.urls import path - +import debug_toolbar import blog.views urlpatterns = [ path('admin/', admin.site.urls), path("", blog.views.index), path("post//", blog.views.post_detail, name="blog-post-detail"), - + path("ip/", blog.views.get_ip), ] + +if settings.DEBUG: + urlpatterns += [ + path("__debug__/", include(debug_toolbar.urls)), + ] +dir diff --git a/blog/models.py b/blog/models.py index f74f94b018..ef1ed4ba6f 100644 --- a/blog/models.py +++ b/blog/models.py @@ -15,9 +15,9 @@ 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() + object_id = models.PositiveIntegerField(db_index=True) content_object = GenericForeignKey("content_type", "object_id") - created_at = models.DateTimeField(auto_now_add=True) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) modified_at = models.DateTimeField(auto_now=True) @@ -25,7 +25,7 @@ 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) + 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) diff --git a/blog/views.py b/blog/views.py index c0a4a9fd62..cb6821a4f2 100644 --- a/blog/views.py +++ b/blog/views.py @@ -7,6 +7,14 @@ # Create your views here. def index(request): posts = Post.objects.filter(published_at__lte=timezone.now()) + ''' + DATABASE QUERY OPTIMISATION...USE ONLY IF A PERMANENT QUERY SET + #.select_related("author") + #.defer("created_at", "modified_at") + #or the oppossite use + #.only("title", "summary", "content", "author", "published_at", "slug") + ''' + return render(request, "blog/index.html", {"posts": posts}) @@ -31,4 +39,8 @@ def post_detail(request, slug): #return render(request, "blog/post-detail.html", {"post": post}) return render(request, "blog/post-detail.html", {"post": post, "comment_form": comment_form} -) \ No newline at end of file +) + +def get_ip(request): + from django.http import HttpResponse + return HttpResponse(request.META['REMOTE_ADDR']) \ No newline at end of file From 27805d930d9d625cd050260c5d5b239ee1701839 Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 6 May 2024 01:10:23 +0200 Subject: [PATCH 16/46] change --- blango/settings.py | 204 +++++++++++++++++++--------------------- blango/settings_orig.py | 160 +++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 109 deletions(-) create mode 100644 blango/settings_orig.py diff --git a/blango/settings.py b/blango/settings.py index 618cb250b6..3e7684513b 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -1,61 +1,64 @@ -""" -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/ -""" - -from pathlib import Path -import os - - -# 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' +""" +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/ +""" +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-&!=9y436&^-bc$qia-mxngyf&xx)@ct)8lu@)=qxg_07-=z01w' +# 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 = [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', ] -#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.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 @@ -65,38 +68,41 @@ 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', + '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', - ], - }, - }, + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + + }, ] -WSGI_APPLICATION = 'blango.wsgi.application' +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', +# 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 +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { @@ -114,44 +120,24 @@ ] -# Internationalization -# https://docs.djangoproject.com/en/3.2/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ -USE_TZ = True +LANGUAGE_CODE = 'en-us' +TIME_ZONE = values.Value("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 files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ +STATIC_URL = '/static/' -STATIC_URL = '/static/' +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field -# 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 +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" +CRISPY_TEMPLATE_PACK = "bootstrap5" \ No newline at end of file diff --git a/blango/settings_orig.py b/blango/settings_orig.py new file mode 100644 index 0000000000..c0d028132a --- /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 From 18c6841278cbce1f5b2a8577365ebb352b62d1f7 Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 6 May 2024 04:07:19 +0200 Subject: [PATCH 17/46] auth added --- blango/data.json | 1 + blango_auth/__init__.py | 0 blango_auth/admin.py | 8 ++++++++ blango_auth/apps.py | 6 ++++++ blango_auth/migrations/__init__.py | 0 blango_auth/models.py | 7 +++++++ blango_auth/tests.py | 3 +++ blango_auth/views.py | 3 +++ blog/data.json | 1 + manage.py | 2 +- 10 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 blango/data.json create mode 100644 blango_auth/__init__.py create mode 100644 blango_auth/admin.py create mode 100644 blango_auth/apps.py create mode 100644 blango_auth/migrations/__init__.py create mode 100644 blango_auth/models.py create mode 100644 blango_auth/tests.py create mode 100644 blango_auth/views.py create mode 100644 blog/data.json 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": "

Content of Test Post

\r\n

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

\r\n

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": "

title of post 3

\r\n

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_auth/__init__.py b/blango_auth/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blango_auth/admin.py b/blango_auth/admin.py new file mode 100644 index 0000000000..88152ce3a2 --- /dev/null +++ b/blango_auth/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from blango_auth.models import User +# Register your models here. + + + +admin.site.register(User, UserAdmin) \ No newline at end of file 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/migrations/__init__.py b/blango_auth/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blango_auth/models.py b/blango_auth/models.py new file mode 100644 index 0000000000..e1a93f8a79 --- /dev/null +++ b/blango_auth/models.py @@ -0,0 +1,7 @@ +from django.db import models +from django.contrib.auth.models import AbstractUser +# Create your models here. + + +class User(AbstractUser): +pass 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..91ea44a218 --- /dev/null +++ b/blango_auth/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/blog/data.json b/blog/data.json new file mode 100644 index 0000000000..bcb6ea11ef --- /dev/null +++ b/blog/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": "

Content of Test Post

\r\n

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

\r\n

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": "

title of post 3

\r\n

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/manage.py b/manage.py index bb13551cdd..935561e3a7 100644 --- a/manage.py +++ b/manage.py @@ -7,7 +7,7 @@ def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blango.settings') - os.environ.setdefault("DJANGO_CONFIGURATION", "Dev") #--added to read configurations + #os.environ.setdefault("DJANGO_CONFIGURATION", "Dev") #--added to read configurations try: #from django.core.management import execute_from_command_line From f36c4faa48dc5a2913996ded2e3623c22fff22e5 Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 6 May 2024 04:08:11 +0200 Subject: [PATCH 18/46] settings_auth --- blango/settings.py | 3 +++ blango/urls.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/blango/settings.py b/blango/settings.py index 3e7684513b..560f9fc09b 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -48,8 +48,11 @@ class Dev(Configuration): 'blog', 'crispy_forms', 'crispy_bootstrap5', + 'blango_auth', ] +AUTH_USER_MODEL = "blango_auth.User" + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', diff --git a/blango/urls.py b/blango/urls.py index 163d4076c0..38c953ae7c 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -29,4 +29,4 @@ urlpatterns += [ path("__debug__/", include(debug_toolbar.urls)), ] -dir + From 638e915c5afd2a029ea5c929a2cdd501a416232a Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 6 May 2024 04:37:39 +0200 Subject: [PATCH 19/46] profile --- blango_auth/models.py | 4 +++- blog/admin.py | 4 +++- blog/models.py | 8 ++++++++ templates/base.html | 18 ++++++++++++++++-- templates/blog/post-detail.html | 9 ++++++++- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/blango_auth/models.py b/blango_auth/models.py index e1a93f8a79..f001a6c356 100644 --- a/blango_auth/models.py +++ b/blango_auth/models.py @@ -1,7 +1,9 @@ from django.db import models -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import AbstractUser, UserManager # Create your models here. class User(AbstractUser): pass + + diff --git a/blog/admin.py b/blog/admin.py index 38862c7928..c7631bbbf1 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -3,7 +3,7 @@ # Register your models here. -from blog.models import Tag, Post, Comment +from blog.models import Tag, Post, Comment, AuthorProfile class PostAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("title",)} @@ -15,4 +15,6 @@ class PostAdmin(admin.ModelAdmin): admin.site.register(Post, PostAdmin) admin.site.register(Comment) +admin.site.register(AuthorProfile) + diff --git a/blog/models.py b/blog/models.py index ef1ed4ba6f..62dcd48e20 100644 --- a/blog/models.py +++ b/blog/models.py @@ -35,4 +35,12 @@ class Post(models.Model): 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/templates/base.html b/templates/base.html index 849ed2c0e7..d5a89fdb5a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,12 +4,26 @@ - Hello, world! + {% block title %}Welcome to Blango{% endblock %} +

Hello World

- + + {% block content %} diff --git a/templates/blog/post-detail.html b/templates/blog/post-detail.html index d3f2072545..06e9066e3b 100644 --- a/templates/blog/post-detail.html +++ b/templates/blog/post-detail.html @@ -14,7 +14,14 @@

{{ post.title }}

{% endrow %} - +{% if post.author.profile %} +{% row %} +{% col %} +

About the author

+

{{ post.author.profile.bio }}

+{% endcol %} +{% endrow %} +{% endif %} {% include "blog/post-comments.html" %} From a6960a4b2911ab02315f073df3c43fece4eb0431 Mon Sep 17 00:00:00 2001 From: distance Date: Wed, 8 May 2024 23:22:04 +0200 Subject: [PATCH 20/46] auth --- blango/urls.py | 5 ++- blango_auth/models.py | 2 +- .../templates/blango_auth/profile.html | 11 +++++++ blango_auth/templates/registration/login.html | 31 +++++++++++++++++++ blog/models.py | 2 +- 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 blango_auth/templates/blango_auth/profile.html create mode 100644 blango_auth/templates/registration/login.html diff --git a/blango/urls.py b/blango/urls.py index 38c953ae7c..5635489925 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -14,15 +14,18 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +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//", blog.views.post_detail, name="blog-post-detail"), path("ip/", blog.views.get_ip), + path("accounts/", include("django.contrib.auth.urls")), + path("accounts/profile/", blango_auth.views.profile, name="profile"), ] if settings.DEBUG: diff --git a/blango_auth/models.py b/blango_auth/models.py index f001a6c356..eec6c516e3 100644 --- a/blango_auth/models.py +++ b/blango_auth/models.py @@ -4,6 +4,6 @@ class User(AbstractUser): -pass + pass diff --git a/blango_auth/templates/blango_auth/profile.html b/blango_auth/templates/blango_auth/profile.html new file mode 100644 index 0000000000..5e51727c25 --- /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/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" %} +
+{% csrf_token %} +{{ form|crispy }} + + +
+

Lost password? +

+{% endcol %} +{% endrow %} +{% endblock %} \ No newline at end of file diff --git a/blog/models.py b/blog/models.py index 62dcd48e20..620d03a5e4 100644 --- a/blog/models.py +++ b/blog/models.py @@ -41,6 +41,6 @@ class AuthorProfile(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile") bio = models.TextField() - def __str__(self): + def __str__(self): return f"{self.__class__.__name__} object for {self.user}" From 63cba82ac917b720ff7471dc622bfba47f3bbe37 Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 13 May 2024 21:43:15 +0200 Subject: [PATCH 21/46] serializer --- blango/settings.py | 44 +++++++++++---------- blog/api/__init__.py | 0 blog/api/serializers.py | 9 +++++ blog/api_urls.py | 8 ++++ blog/api_views.py | 87 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 blog/api/__init__.py create mode 100644 blog/api/serializers.py create mode 100644 blog/api_urls.py create mode 100644 blog/api_views.py diff --git a/blango/settings.py b/blango/settings.py index 560f9fc09b..39bd4e76b3 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -8,13 +8,13 @@ """ import os from pathlib import Path -from configurations import Configuration -from configurations import values +#from configurations import Configuration +#from configurations import values -class Dev(Configuration): +#class Dev(Configuration): # Build paths inside the project like this: BASE_DIR / 'subdir'. - BASE_DIR = Path(__file__).resolve().parent.parent +BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production @@ -22,19 +22,22 @@ class Dev(Configuration): # 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! +# 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 = [os.environ.get('CODIO_HOSTNAME') + '-8000.codio.io'] -CSRF_COOKIE_SECURE = True -SESSION_COOKIE_SECURE = True -CSRF_COOKIE_SAMESITE = 'None' -SESSION_COOKIE_SAMESITE = 'None' +AUTH_USER_MODEL = "blango_auth.User" + + + +#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 @@ -47,18 +50,17 @@ class Dev(Configuration): 'django.contrib.staticfiles', 'blog', 'crispy_forms', - 'crispy_bootstrap5', - 'blango_auth', + 'crispy_bootstrap5', + 'blango_auth', + 'rest_framework', ] -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.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', @@ -69,7 +71,7 @@ class Dev(Configuration): internet. ''' -ROOT_URLCONF = 'blango.urls' +ROOT_URLCONF = 'blango3.urls' TEMPLATES = [ @@ -90,7 +92,7 @@ class Dev(Configuration): ] -WSGI_APPLICATION = 'blango.wsgi.application' +WSGI_APPLICATION = 'blango3.wsgi.application' # Database @@ -127,7 +129,7 @@ class Dev(Configuration): # https://docs.djangoproject.com/en/3.2/topics/i18n/ LANGUAGE_CODE = 'en-us' -TIME_ZONE = values.Value("UTC") +TIME_ZONE = ("UTC") USE_I18N = True USE_L10N = True USE_TZ = True diff --git a/blog/api/__init__.py b/blog/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blog/api/serializers.py b/blog/api/serializers.py new file mode 100644 index 0000000000..76084c2865 --- /dev/null +++ b/blog/api/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers +from blog.models import Post + + +class PostSerializer(serializers.ModelSerializer): + class Meta: + model = Post + fields = "__all__" + readonly = ["modified_at", "created_at"] diff --git a/blog/api_urls.py b/blog/api_urls.py new file mode 100644 index 0000000000..4f95d4e5dd --- /dev/null +++ b/blog/api_urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from blog.api_views import post_list, post_detail + +urlpatterns = [ + path("posts/", post_list, name="api_post_list"), + path("posts//", post_detail, name="api_post_detail"), +] diff --git a/blog/api_views.py b/blog/api_views.py new file mode 100644 index 0000000000..169702d107 --- /dev/null +++ b/blog/api_views.py @@ -0,0 +1,87 @@ +import json +from http import HTTPStatus + +from blog.api.serializers import PostSerializer + +from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed +from django.shortcuts import get_object_or_404 +from django.urls import reverse +from django.views.decorators.csrf import csrf_exempt + +from blog.models import Post + + +def post_to_dict(post): + return { + "pk": post.pk, + "author_id": post.author_id, + "created_at": post.created_at, + "modified_at": post.modified_at, + "published_at": post.published_at, + "title": post.title, + "slug": post.slug, + "summary": post.summary, + "content": post.content, + } + + +@csrf_exempt +def post_list(request): + if request.method == "GET": + posts = Post.objects.all() + return JsonResponse({"data": PostSerializer(posts, many=True).data}) + + ''' + #posts = Post.objects.all() + #posts_as_dict = [post_to_dict(p) for p in posts] + #return JsonResponse({"data": posts_as_dict}) + ''' + elif request.method == "POST": + post_data = json.loads(request.body) + post = Post.objects.create(**post_data) + + serializer = PostSerializer(data=post_data) + serializer.is_valid(raise_exception=True) + post = serializer.save() + + + return HttpResponse( + status=HTTPStatus.CREATED, + headers={"Location": reverse("api_post_detail", args=(post.pk,))}, + ) + + + + return HttpResponseNotAllowed(["GET", "POST"]) + + +@csrf_exempt +def post_detail(request, pk): + post = get_object_or_404(Post, pk=pk) + + if request.method == "GET": + return JsonResponse(PostSerializer(post).data) + # return JsonResponse(post_to_dict(post)) + elif request.method == "PUT": + post_data = json.loads(request.body) + for field, value in post_data.items(): + setattr(post, field, value) + #post.save() + serializer = PostSerializer(post, data=post_data) + serializer.is_valid(raise_exception=True) + serializer.save() + + return HttpResponse(status=HTTPStatus.NO_CONTENT) + + + elif request.method == "DELETE": + post.delete() + return HttpResponse(status=HTTPStatus.NO_CONTENT) + + return HttpResponseNotAllowed(["GET", "PUT", "DELETE"]) + + + + + + From fa2720f286dfdee77cee9602a6387846dc7b9f7e Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 13 May 2024 23:39:20 +0200 Subject: [PATCH 22/46] api_alignment --- blog/api/urls.py | 11 ++++++ blog/api/views.py | 14 ++++++++ blog/api_urls.py | 8 ----- blog/api_views.py | 87 ----------------------------------------------- 4 files changed, 25 insertions(+), 95 deletions(-) create mode 100644 blog/api/urls.py create mode 100644 blog/api/views.py delete mode 100644 blog/api_urls.py delete mode 100644 blog/api_views.py diff --git a/blog/api/urls.py b/blog/api/urls.py new file mode 100644 index 0000000000..39fdd47a32 --- /dev/null +++ b/blog/api/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from rest_framework.urlpatterns import format_suffix_patterns + +from blog.api.views import PostList, PostDetail + +urlpatterns = [ + path("posts/", PostList.as_view(), name="api_post_list"), + path("posts/", PostDetail.as_view(), name="api_post_detail"), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/blog/api/views.py b/blog/api/views.py new file mode 100644 index 0000000000..46c3e3e51b --- /dev/null +++ b/blog/api/views.py @@ -0,0 +1,14 @@ +from rest_framework import generics + +from blog.api.serializers import PostSerializer +from blog.models import Post + + +class PostList(generics.ListCreateAPIView): + queryset = Post.objects.all() + serializer_class = PostSerializer + + +class PostDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Post.objects.all() + serializer_class = PostSerializer diff --git a/blog/api_urls.py b/blog/api_urls.py deleted file mode 100644 index 4f95d4e5dd..0000000000 --- a/blog/api_urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path - -from blog.api_views import post_list, post_detail - -urlpatterns = [ - path("posts/", post_list, name="api_post_list"), - path("posts//", post_detail, name="api_post_detail"), -] diff --git a/blog/api_views.py b/blog/api_views.py deleted file mode 100644 index 169702d107..0000000000 --- a/blog/api_views.py +++ /dev/null @@ -1,87 +0,0 @@ -import json -from http import HTTPStatus - -from blog.api.serializers import PostSerializer - -from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed -from django.shortcuts import get_object_or_404 -from django.urls import reverse -from django.views.decorators.csrf import csrf_exempt - -from blog.models import Post - - -def post_to_dict(post): - return { - "pk": post.pk, - "author_id": post.author_id, - "created_at": post.created_at, - "modified_at": post.modified_at, - "published_at": post.published_at, - "title": post.title, - "slug": post.slug, - "summary": post.summary, - "content": post.content, - } - - -@csrf_exempt -def post_list(request): - if request.method == "GET": - posts = Post.objects.all() - return JsonResponse({"data": PostSerializer(posts, many=True).data}) - - ''' - #posts = Post.objects.all() - #posts_as_dict = [post_to_dict(p) for p in posts] - #return JsonResponse({"data": posts_as_dict}) - ''' - elif request.method == "POST": - post_data = json.loads(request.body) - post = Post.objects.create(**post_data) - - serializer = PostSerializer(data=post_data) - serializer.is_valid(raise_exception=True) - post = serializer.save() - - - return HttpResponse( - status=HTTPStatus.CREATED, - headers={"Location": reverse("api_post_detail", args=(post.pk,))}, - ) - - - - return HttpResponseNotAllowed(["GET", "POST"]) - - -@csrf_exempt -def post_detail(request, pk): - post = get_object_or_404(Post, pk=pk) - - if request.method == "GET": - return JsonResponse(PostSerializer(post).data) - # return JsonResponse(post_to_dict(post)) - elif request.method == "PUT": - post_data = json.loads(request.body) - for field, value in post_data.items(): - setattr(post, field, value) - #post.save() - serializer = PostSerializer(post, data=post_data) - serializer.is_valid(raise_exception=True) - serializer.save() - - return HttpResponse(status=HTTPStatus.NO_CONTENT) - - - elif request.method == "DELETE": - post.delete() - return HttpResponse(status=HTTPStatus.NO_CONTENT) - - return HttpResponseNotAllowed(["GET", "PUT", "DELETE"]) - - - - - - From abb58d4787de8643b825d7fb9a07b817d58468d8 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 19 May 2024 20:44:13 +0200 Subject: [PATCH 23/46] Finish authentication --- blango/__pycache__/settings.cpython-311.pyc | Bin 2800 -> 3836 bytes blango/settings.py | 282 ++++++++++---------- blango/settings_orig.py | 4 +- blango/urls.py | 1 + blango_auth/views.py | 7 + blog/api/urls.py | 12 +- manage.py | 3 +- 7 files changed, 170 insertions(+), 139 deletions(-) diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc index 29d720adc7d9ea514bfda39811a9c23e81f6e9c5..4e56f86922b0c79d60130e24b1282be104f6ce20 100644 GIT binary patch delta 1997 zcma)7OKcNY6n$@I{D~8P;@D2&{G5-IOoW|KQXsS-j58rA!9zWcpd*y2@tYWfV}m~- zxC_ss3o2DrkyU}JNJXfE1xvQ=y4!ITsiRp`NL{t7samP1?V@+=q$Cpkym|NB&%5v5 z`#v*c`2QTLKXp1C2wHXVosE}XgnlrU{btz{eo2Dx01+f2f(ez!A_h+9sZCm>v1w!S z%%(-Ov}6m&R?&(Ph0Y^l{SXnGY^bDxoL>r+5#M;x=;~1e(sIh=2C}8XOE#}QA z2s?Pn)Mu%w#!uK|ihtnwwq-sN3ORl><0M$DGndkp>%g7yVqqhft(MZ2T(JNK>sC5n zmCKp?-G1*wNb&&52+BqxMIo8KzkH#NMLVIy4nk+K=pc;PNi1^L4d-5o=#gt*I>?Fj z2?Tov)NVCk#Ac%CCU(*D|0C4*BJ53vBD$d*FHjHZ5IsP?EFzA_So8z$1Rm%>q)Y52 zPT59WwN8Yxa2Ui5qn^h&he`Ji15x^jSL`P}#0Oq}(~AUls2mo9q?h!8S1;*rVLjMl z138>!!~rrW4w4~p2+|J&jR1wnusBLa#4!>Q$AKouD5M%QQ;A_>hw~fP=|y>~c8=Ug z7qZ1ju27aU)sh^U96h~ocmCM%$@R#!%*5?nIu-W-9E-eDy5*-s#)h zS!wP>K%VoICFsXiU)_){Qnaenwh@I5! zl2m}jOOo!8q|G9!=1sXvl5SVi`SwW3d_7Iaja+Nc2GtewTe5Dw)p%)pqwY4BF0Bb% zQo6`5a;rL3EbBJ8u$3zn3p$;ZE4urNw3LLSk@!pI?g+XsE+m(vIL}{L;Uob@gq0Mx zXH6y7gcP?Z@yV6*E8y!rO!)__xmSrZc>2_9MH_W6HGQ zLY@>>W?nd4N?7X&d>U3FFv&lq%o=v&AA6MiQhd7pcwL=eY=*hrFxT{P>hhJQ?}{?3 z^$)33J)i{#cV@nbejZiXg=X;7Zt#?PCeaKg)P7#20$T5YO8KjNxTytI>tLBgzrOfqIv*)-&+%X0wM?y)#-M_`)@2AWJ>$ z1KbDN7VsDyPMaMf1JG_aqp-#V3e2jM7uIU?Xudhk8+>@R>17QI?d>v<3ADv+2ge}l z>{h*34Vr?<_Lh=7s0as@R7SL6s`IOG>A1-UxZIVQa8A&Qlnb+BN3;qxJ zT>V=6cDphMe-q^rV@@=GbM5;PGo@CrI7q+S-)_GtXG-TmHNW}gPXG)WV{A}13t~Z?hI^XjDDi4Th)8V6Vq}k)K;5zy8B`tKh!?Ge{{5 delta 909 zcmYk3O=uHA6vyA}W|RHyCf&B%+DJpKF%1b;;ulI4MG+LWDxz~R#LPBsn7|Ir9z1#V;3*Zs5Iht;>1jalARc_1Ds*<{H}5fT_Pzg`KS@4tVZ$&~ z!119tHT&KI_!c_%tF&$5hb>b0bzKq~4ll9R)X0bR%CBi7y|5ES> zwV2HYFv@0wJr4Egc%%}caW+g7Y=riwF}aOv0M{VfLx1`TV@I-;>_nl^IzD-4?#}((a-o)*J21Xd%{(|>nkY`y7f-iZhq6<- z8(lJ)QG#sbosfwIDQPWuB-whiF`GY&xaqko!G_ez9ofm0U+@^Kx+|rIn=e zJnz*i`CI23r3LrO;*wXdH8%&NuL!X=-^ON0GP9=}D;8P3Tr75Di&%EmyW0>^Mpvje z1Fl>MzNo)~uiB~A-}1?YI$N%~(~X08jvayK5|WFA5H6@B!XbnMWVj8^?%4sw?|obb z-FjSjCO?^eI{jHoebiE0+W4n%PJYs+e3_eIUL(HBqo5eRv>msfNxs2VHL)t-?-M)l zP437h+W&zU5)sU;gO&7k%wnG-o%ryJi(Bz@+d9y(oo#)LgEVgLf*7+^uIN&fLkQ6{ z1YhA}VCULkVIh*)N#r}0^Wx-|mFBCGXklasQR+pGfYR5tC>I61E%%(OyPcWc&b1w9 zz!(*h(;bC F;4gHQ?W_O* diff --git a/blango/settings.py b/blango/settings.py index 39bd4e76b3..8511530204 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -6,143 +6,155 @@ 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 +from configurations import Configuration +from configurations import values -#class Dev(Configuration): +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-&!=9y436&^-bc$qia-mxngyf&xx)@ct)8lu@)=qxg_07-=z01w' -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True -ALLOWED_HOSTS = ['*'] - - -AUTH_USER_MODEL = "blango_auth.User" - - - -#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', -] - - -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 = 'blango3.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 = 'blango3.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" \ No newline at end of file + 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-&!=9y436&^-bc$qia-mxngyf&xx)@ct)8lu@)=qxg_07-=z01w' + # SECURITY WARNING: don't run with debug turned on in production! + DEBUG = True + ALLOWED_HOSTS = ['*'] + + + AUTH_USER_MODEL = "blango_auth.User" + + + + 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', + ] + + + 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', + }, + ] + + + # 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" + + REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.TokenAuthentication", + ] + } \ No newline at end of file diff --git a/blango/settings_orig.py b/blango/settings_orig.py index c0d028132a..62acdb3720 100644 --- a/blango/settings_orig.py +++ b/blango/settings_orig.py @@ -12,8 +12,8 @@ import os from pathlib import Path -from configurations import Configuration -from configurations import values +#from configurations import Configuration +#from configurations import values class Dev(Configuration): diff --git a/blango/urls.py b/blango/urls.py index 5635489925..c80a3533f7 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -26,6 +26,7 @@ path("ip/", blog.views.get_ip), path("accounts/", include("django.contrib.auth.urls")), path("accounts/profile/", blango_auth.views.profile, name="profile"), + path("api/v1/", include("blog.api.urls")), ] if settings.DEBUG: diff --git a/blango_auth/views.py b/blango_auth/views.py index 91ea44a218..36a76d2034 100644 --- a/blango_auth/views.py +++ b/blango_auth/views.py @@ -1,3 +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/api/urls.py b/blog/api/urls.py index 39fdd47a32..7003b6fa37 100644 --- a/blog/api/urls.py +++ b/blog/api/urls.py @@ -1,5 +1,7 @@ -from django.urls import path +from django.urls import path, include from rest_framework.urlpatterns import format_suffix_patterns +from rest_framework.authtoken import views +from blog.api.views import PostList, PostDetail from blog.api.views import PostList, PostDetail @@ -9,3 +11,11 @@ ] urlpatterns = format_suffix_patterns(urlpatterns) + +urlpatterns += [ + path("auth/", include("rest_framework.urls")), + path("token-auth/", views.obtain_auth_token), +]urlpatterns += [ + path("auth/", include("rest_framework.urls")), + path("token-auth/", views.obtain_auth_token), +] diff --git a/manage.py b/manage.py index 935561e3a7..9d249659c7 100644 --- a/manage.py +++ b/manage.py @@ -7,7 +7,7 @@ def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blango.settings') - #os.environ.setdefault("DJANGO_CONFIGURATION", "Dev") #--added to read configurations + os.environ.setdefault("DJANGO_CONFIGURATION", "Dev") #--added to read configurations try: #from django.core.management import execute_from_command_line @@ -24,3 +24,4 @@ def main(): if __name__ == '__main__': main() + From fdf519068377456e6ff704ab8e83e2534ac0bf1d Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 19 May 2024 22:04:57 +0200 Subject: [PATCH 24/46] Finish permissions --- blango/settings.py | 6 +++++- blog/api/permissions.py | 13 +++++++++++++ blog/api/urls.py | 5 +---- blog/api/views.py | 10 ++++++---- 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 blog/api/permissions.py diff --git a/blango/settings.py b/blango/settings.py index 8511530204..bf04548dff 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -156,5 +156,9 @@ class Dev(Configuration): "rest_framework.authentication.BasicAuthentication", "rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.TokenAuthentication", - ] + ], + + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticatedOrReadOnly" + ], } \ No newline at end of file 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/urls.py b/blog/api/urls.py index 7003b6fa37..5ef917dc6d 100644 --- a/blog/api/urls.py +++ b/blog/api/urls.py @@ -15,7 +15,4 @@ urlpatterns += [ path("auth/", include("rest_framework.urls")), path("token-auth/", views.obtain_auth_token), -]urlpatterns += [ - path("auth/", include("rest_framework.urls")), - path("token-auth/", views.obtain_auth_token), -] + diff --git a/blog/api/views.py b/blog/api/views.py index 46c3e3e51b..4822d174e9 100644 --- a/blog/api/views.py +++ b/blog/api/views.py @@ -2,13 +2,15 @@ from blog.api.serializers import PostSerializer from blog.models import Post +from blog.api.permissions import AuthorModifyOrReadOnly, IsAdminUserForObject class PostList(generics.ListCreateAPIView): - queryset = Post.objects.all() - serializer_class = PostSerializer + queryset = Post.objects.all() + serializer_class = PostSerializer class PostDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = Post.objects.all() - serializer_class = PostSerializer + permission_classes = [AuthorModifyOrReadOnly | IsAdminUserForObject] + queryset = Post.objects.all() + serializer_class = PostSerializer \ No newline at end of file From c4c4c629d9b91a04a515f77177cff5a1a07346b2 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 19 May 2024 23:27:01 +0200 Subject: [PATCH 25/46] Finish related fields --- blango/urls.py | 6 ++++-- blog/api/serializers.py | 19 ++++++++++++++++++- blog/api/urls.py | 7 +++---- blog/api/views.py | 11 +++++++++-- blog/models.py | 2 +- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/blango/urls.py b/blango/urls.py index c80a3533f7..8a2b6a84a1 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -15,7 +15,7 @@ """ from django.contrib import admin from django.urls import path, include -import debug_toolbar +#import debug_toolbar import blog.views import blango_auth.views @@ -29,8 +29,10 @@ path("api/v1/", include("blog.api.urls")), ] + +""" if settings.DEBUG: urlpatterns += [ path("__debug__/", include(debug_toolbar.urls)), ] - +""" diff --git a/blog/api/serializers.py b/blog/api/serializers.py index 76084c2865..ca3fe5a91d 100644 --- a/blog/api/serializers.py +++ b/blog/api/serializers.py @@ -1,9 +1,26 @@ from rest_framework import serializers -from blog.models import Post +from blog.models import Post, Tag +from blango_auth.models import User + 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" + ) + class Meta: model = Post fields = "__all__" readonly = ["modified_at", "created_at"] + + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["first_name", "last_name", "email"] diff --git a/blog/api/urls.py b/blog/api/urls.py index 5ef917dc6d..a894de3621 100644 --- a/blog/api/urls.py +++ b/blog/api/urls.py @@ -1,13 +1,12 @@ from django.urls import path, include from rest_framework.urlpatterns import format_suffix_patterns from rest_framework.authtoken import views -from blog.api.views import PostList, PostDetail - -from blog.api.views import PostList, PostDetail +from blog.api.views import PostList, PostDetail, UserDetail urlpatterns = [ path("posts/", PostList.as_view(), name="api_post_list"), path("posts/", PostDetail.as_view(), name="api_post_detail"), + path("users/", UserDetail.as_view(), name="api_user_detail"), ] urlpatterns = format_suffix_patterns(urlpatterns) @@ -15,4 +14,4 @@ urlpatterns += [ path("auth/", include("rest_framework.urls")), path("token-auth/", views.obtain_auth_token), - +] diff --git a/blog/api/views.py b/blog/api/views.py index 4822d174e9..94146bca8a 100644 --- a/blog/api/views.py +++ b/blog/api/views.py @@ -1,6 +1,8 @@ from rest_framework import generics -from blog.api.serializers import PostSerializer +from blango_auth.models import User +from blog.api.serializers import PostSerializer, UserSerializer + from blog.models import Post from blog.api.permissions import AuthorModifyOrReadOnly, IsAdminUserForObject @@ -13,4 +15,9 @@ class PostList(generics.ListCreateAPIView): class PostDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = [AuthorModifyOrReadOnly | IsAdminUserForObject] queryset = Post.objects.all() - serializer_class = PostSerializer \ No newline at end of file + serializer_class = PostSerializer + +class UserDetail(generics.RetrieveAPIView): + lookup_field = "email" + queryset = User.objects.all() + serializer_class = UserSerializer diff --git a/blog/models.py b/blog/models.py index 620d03a5e4..74be1a7e0c 100644 --- a/blog/models.py +++ b/blog/models.py @@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType class Tag(models.Model): - value = models.TextField(max_length=100) + value = models.TextField(max_length=100, unique=True) def __str__(self): return self.value From 7892ab7ffa79fe2f769731c665ce98018722065c Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 20 May 2024 00:12:44 +0200 Subject: [PATCH 26/46] Finish nested relationships --- blog/api/serializers.py | 55 +++++++++++++++++++++++++++++++++++++---- blog/api/views.py | 5 ++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/blog/api/serializers.py b/blog/api/serializers.py index ca3fe5a91d..dba0ad6dcd 100644 --- a/blog/api/serializers.py +++ b/blog/api/serializers.py @@ -1,9 +1,31 @@ from rest_framework import serializers -from blog.models import Post, Tag +from blog.models import Post, Tag, Comment + from blango_auth.models import User +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() @@ -20,7 +42,30 @@ class Meta: -class UserSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = ["first_name", "last_name", "email"] +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 PostDetailSerializer(PostSerializer): + comments = CommentSerializer(many=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 + diff --git a/blog/api/views.py b/blog/api/views.py index 94146bca8a..5911f29bed 100644 --- a/blog/api/views.py +++ b/blog/api/views.py @@ -1,7 +1,8 @@ from rest_framework import generics from blango_auth.models import User -from blog.api.serializers import PostSerializer, UserSerializer +from blog.api.serializers import PostSerializer, UserSerializer, PostDetailSerializer + from blog.models import Post from blog.api.permissions import AuthorModifyOrReadOnly, IsAdminUserForObject @@ -15,7 +16,7 @@ class PostList(generics.ListCreateAPIView): class PostDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = [AuthorModifyOrReadOnly | IsAdminUserForObject] queryset = Post.objects.all() - serializer_class = PostSerializer + serializer_class = PostDetailSerializer class UserDetail(generics.RetrieveAPIView): lookup_field = "email" From ede9c0cc9f2a729f8b5c3e8c8006ab9009af8fca Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 20 May 2024 12:12:10 +0200 Subject: [PATCH 27/46] Finish browsable API --- blango/settings.py | 10 +++++++++- blog/api/urls.py | 27 ++++++++++++++++++++++++++- templates/rest_framework/api.html | 19 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 templates/rest_framework/api.html diff --git a/blango/settings.py b/blango/settings.py index bf04548dff..bc25a87d24 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -57,6 +57,7 @@ class Dev(Configuration): 'blango_auth', 'rest_framework', 'rest_framework.authtoken', + 'drf_yasg', ] @@ -161,4 +162,11 @@ class Dev(Configuration): "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticatedOrReadOnly" ], - } \ No newline at end of file + } + + SWAGGER_SETTINGS = { + "SECURITY_DEFINITIONS": { + "Token": {"type": "apiKey", "name": "Authorization", "in": "header"}, + "Basic": {"type": "basic"}, + } + } \ No newline at end of file diff --git a/blog/api/urls.py b/blog/api/urls.py index a894de3621..f1f1851c90 100644 --- a/blog/api/urls.py +++ b/blog/api/urls.py @@ -1,4 +1,8 @@ -from django.urls import path, include +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.urlpatterns import format_suffix_patterns from rest_framework.authtoken import views from blog.api.views import PostList, PostDetail, UserDetail @@ -11,7 +15,28 @@ urlpatterns = format_suffix_patterns(urlpatterns) +schema_view = get_schema_view( + openapi.Info( + title="Blango API", + default_version="v1", + description="API for Blango Blog", + ), + url=f"https://{os.environ.get('CODIO_HOSTNAME')}-8000.codio.io/api/v1/", + public=True, +) + + urlpatterns += [ path("auth/", include("rest_framework.urls")), path("token-auth/", views.obtain_auth_token), + re_path( + r"^swagger(?P\.json|\.yaml)$", + schema_view.without_ui(cache_timeout=0), + name="schema-json", + ), + path( + "swagger/", + schema_view.with_ui("swagger", cache_timeout=0), + name="schema-swagger-ui", + ), ] diff --git a/templates/rest_framework/api.html b/templates/rest_framework/api.html new file mode 100644 index 0000000000..54cb7bc898 --- /dev/null +++ b/templates/rest_framework/api.html @@ -0,0 +1,19 @@ +{% extends "rest_framework/base.html" %} +{% block title %}{% if name %}{{ name }} – {% endif %} Blango REST API{% endblock %} + + +{% block branding %} + + Blango REST API + +{% endblock %} + + +{% block style %} + {{ block.super }} + +{% endblock %} \ No newline at end of file From e40e148ce9db9eb47c927ce1f8544880e81f939e Mon Sep 17 00:00:00 2001 From: distance Date: Tue, 21 May 2024 07:06:25 +0200 Subject: [PATCH 28/46] Finish viewsets and routers --- blango/settings.py | 12 +++++------ blog/api/serializers.py | 7 +++++++ blog/api/urls.py | 20 ++++++++++++++++--- blog/api/views.py | 44 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 70 insertions(+), 13 deletions(-) diff --git a/blango/settings.py b/blango/settings.py index bc25a87d24..ab0387b3e0 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -164,9 +164,9 @@ class Dev(Configuration): ], } - SWAGGER_SETTINGS = { - "SECURITY_DEFINITIONS": { - "Token": {"type": "apiKey", "name": "Authorization", "in": "header"}, - "Basic": {"type": "basic"}, - } - } \ No newline at end of file +SWAGGER_SETTINGS = { + "SECURITY_DEFINITIONS": { + "Token": {"type": "apiKey", "name": "Authorization", "in": "header"}, + "Basic": {"type": "basic"}, + } +} \ No newline at end of file diff --git a/blog/api/serializers.py b/blog/api/serializers.py index dba0ad6dcd..d82951dba7 100644 --- a/blog/api/serializers.py +++ b/blog/api/serializers.py @@ -50,6 +50,13 @@ def to_internal_value(self, data): 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) diff --git a/blog/api/urls.py b/blog/api/urls.py index f1f1851c90..c25d1df8df 100644 --- a/blog/api/urls.py +++ b/blog/api/urls.py @@ -3,13 +3,25 @@ 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 -from blog.api.views import PostList, PostDetail, UserDetail + + +#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/", PostDetail.as_view(), name="api_post_detail"), + #path("posts/", PostList.as_view(), name="api_post_list"), + #path("posts/", PostDetail.as_view(), name="api_post_detail"), path("users/", UserDetail.as_view(), name="api_user_detail"), ] @@ -29,6 +41,8 @@ urlpatterns += [ path("auth/", include("rest_framework.urls")), path("token-auth/", views.obtain_auth_token), + path("", include(router.urls)), + re_path( r"^swagger(?P\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), diff --git a/blog/api/views.py b/blog/api/views.py index 5911f29bed..0358a2364a 100644 --- a/blog/api/views.py +++ b/blog/api/views.py @@ -1,13 +1,21 @@ -from rest_framework import generics + +from rest_framework import generics, viewsets +from rest_framework.decorators import action +from rest_framework.response import Response from blango_auth.models import User -from blog.api.serializers import PostSerializer, UserSerializer, PostDetailSerializer +from blog.api.serializers import ( + PostSerializer, + UserSerializer, + PostDetailSerializer, + TagSerializer, +) +from blog.models import Post, Tag -from blog.models import Post from blog.api.permissions import AuthorModifyOrReadOnly, IsAdminUserForObject - +""" class PostList(generics.ListCreateAPIView): queryset = Post.objects.all() serializer_class = PostSerializer @@ -18,7 +26,35 @@ class PostDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Post.objects.all() serializer_class = PostDetailSerializer +""" +class PostViewSet(viewsets.ModelViewSet): + permission_classes = [AuthorModifyOrReadOnly | IsAdminUserForObject] + queryset = Post.objects.all() + + def get_serializer_class(self): + if self.action in ("list", "create"): + return PostSerializer + return PostDetailSerializer + + class UserDetail(generics.RetrieveAPIView): lookup_field = "email" queryset = User.objects.all() serializer_class = UserSerializer + + +class TagViewSet(viewsets.ModelViewSet): + queryset = Tag.objects.all() + serializer_class = TagSerializer + + @action(methods=["get"], detail=True, name="Posts with the Tag") + def posts(self, request, pk=None): + tag = self.get_object() + post_serializer = PostSerializer( + tag.posts, many=True, context={"request": request} + ) + return Response(post_serializer.data) + +""" +https://arieldomino-jargontarget-8000.codio.io/api/v1/tags/1/ +""" \ No newline at end of file From c11d48c95b6081e8919bef2857e41a699c19c4f7 Mon Sep 17 00:00:00 2001 From: distance Date: Tue, 21 May 2024 11:06:27 +0200 Subject: [PATCH 29/46] Finish testing DRF with mocks --- blog/test_post_api.py | 111 ++++++++++++++++++++++++++++++++++++++++++ db(1).sqlite3 | Bin 0 -> 217088 bytes 2 files changed, 111 insertions(+) create mode 100644 blog/test_post_api.py create mode 100644 db(1).sqlite3 diff --git a/blog/test_post_api.py b/blog/test_post_api.py new file mode 100644 index 0000000000..05803eebb8 --- /dev/null +++ b/blog/test_post_api.py @@ -0,0 +1,111 @@ +from datetime import datetime + +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.utils import timezone +from pytz import UTC +from rest_framework.authtoken.models import Token +from rest_framework.test import APIClient + +from blog.models import Post + + + +class PostApiTestCase(TestCase): + + def setUp(self): + self.u1 = get_user_model().objects.create_user( + email="test@example.com", password="password" + + ) + + + self.u2 = get_user_model().objects.create_user( + + email="test2@example.com", password="password2" + + ) + + posts = [ + Post.objects.create( + author=self.u1, + published_at=timezone.now(), + title="Post 1 Title", + slug="post-1-slug", + summary="Post 1 Summary", + content="Post 1 Content", + ), + Post.objects.create( + author=self.u2, + published_at=timezone.now(), + title="Post 2 Title", + slug="post-2-slug", + summary="Post 2 Summary", + content="Post 2 Content", + ), + ] + + # let us look up the post info by ID + self.post_lookup = {p.id: p for p in posts} + + # override test client + self.client = APIClient() + token = Token.objects.create(user=self.u1) + self.client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + + def test_post_list(self): + resp = self.client.get("/api/v1/posts/") + data = resp.json() + self.assertEqual(len(data), 2) + + for post_dict in data: + post_obj = self.post_lookup[post_dict["id"]] + self.assertEqual(post_obj.title, post_dict["title"]) + self.assertEqual(post_obj.slug, post_dict["slug"]) + self.assertEqual(post_obj.summary, post_dict["summary"]) + self.assertEqual(post_obj.content, post_dict["content"]) + self.assertTrue( + post_dict["author"].endswith(f"/api/v1/users/{post_obj.author.email}") + ) + self.assertEqual( + post_obj.published_at, + datetime.strptime( + post_dict["published_at"], "%Y-%m-%dT%H:%M:%S.%fZ" + ).replace(tzinfo=UTC), + ) + + + def test_unauthenticated_post_create(self): + # unset credentials so we are an anonymous user + self.client.credentials() + post_dict = { + "title": "Test Post", + "slug": "test-post-3", + "summary": "Test Summary", + "content": "Test Content", + "author": "http://testserver/api/v1/users/test@example.com", + "published_at": "2021-01-10T09:00:00Z", + } + resp = self.client.post("/api/v1/posts/", post_dict) + self.assertEqual(resp.status_code, 401) + self.assertEqual(Post.objects.all().count(), 2) + + def test_post_create(self): + post_dict = { + "title": "Test Post", + "slug": "test-post-3", + "summary": "Test Summary", + "content": "Test Content", + "author": "http://testserver/api/v1/users/test@example.com", + "published_at": "2021-01-10T09:00:00Z", + } + resp = self.client.post("/api/v1/posts/", post_dict) + post_id = resp.json()["id"] + post = Post.objects.get(pk=post_id) + self.assertEqual(post.title, post_dict["title"]) + self.assertEqual(post.slug, post_dict["slug"]) + self.assertEqual(post.summary, post_dict["summary"]) + self.assertEqual(post.content, post_dict["content"]) + self.assertEqual(post.author, self.u1) + self.assertEqual(post.published_at, datetime(2021, 1, 10, 9, 0, 0, tzinfo=UTC)) diff --git a/db(1).sqlite3 b/db(1).sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..2b00c53ef653cae9356c734326d7753b855418aa GIT binary patch literal 217088 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 literal 0 HcmV?d00001 From 3081b10c4504a72605f27ba24b43d641b92beb45 Mon Sep 17 00:00:00 2001 From: distance Date: Tue, 21 May 2024 11:07:57 +0200 Subject: [PATCH 30/46] dbUpdate --- db(1).sqlite3 | Bin 217088 -> 0 bytes db.sqlite3 | Bin 180224 -> 217088 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 db(1).sqlite3 diff --git a/db(1).sqlite3 b/db(1).sqlite3 deleted file mode 100644 index 2b00c53ef653cae9356c734326d7753b855418aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217088 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 diff --git a/db.sqlite3 b/db.sqlite3 index f05f31f02e52332bf29516e6927f592a7202a99e..2b00c53ef653cae9356c734326d7753b855418aa 100644 GIT binary patch delta 6406 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 TNa Date: Tue, 21 May 2024 21:18:15 +0200 Subject: [PATCH 31/46] Finish caching --- blog/api/views.py | 58 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/blog/api/views.py b/blog/api/views.py index 0358a2364a..d8130ac7f1 100644 --- a/blog/api/views.py +++ b/blog/api/views.py @@ -1,3 +1,10 @@ +#caching imports +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page +from django.views.decorators.vary import vary_on_headers, vary_on_cookie + +from rest_framework.exceptions import PermissionDenied +#end caching imports from rest_framework import generics, viewsets from rest_framework.decorators import action @@ -36,12 +43,45 @@ def get_serializer_class(self): return PostSerializer return PostDetailSerializer - +#caching function starts here# + + @method_decorator(cache_page(300)) + @method_decorator(vary_on_headers("Authorization")) + @method_decorator(vary_on_cookie) + @action(methods=["get"], detail=False, name="Posts by the logged in user") + def mine(self, request): + if request.user.is_anonymous: + raise PermissionDenied("You must be logged in to see which Posts are yours") + posts = self.get_queryset().filter(author=request.user) + serializer = PostSerializer(posts, many=True, context={"request": request}) + return Response(serializer.data) + +#We also want to cache the list of Posts for two +# minutes, which means overriding the list() view. Implement this list() # + @method_decorator(cache_page(120)) + def list(self, *args, **kwargs): + return super(PostViewSet, self).list(*args, **kwargs) + +#--caching ends--# + + class UserDetail(generics.RetrieveAPIView): lookup_field = "email" queryset = User.objects.all() serializer_class = UserSerializer - + + + +#caching function starts here# + ##Since this is a view, and not a viewset, we want to override and cache on the view methods. + @method_decorator(cache_page(300)) + def get(self, *args, **kwargs): + return super(UserDetail, self).get(*args, *kwargs) + + +#--caching ends--# + + class TagViewSet(viewsets.ModelViewSet): queryset = Tag.objects.all() @@ -55,6 +95,20 @@ def posts(self, request, pk=None): ) return Response(post_serializer.data) +#caching function starts here# +#We’ll add caching to both the list() and retrieve() methods. # + + @method_decorator(cache_page(300)) + def list(self, *args, **kwargs): + return super(TagViewSet, self).list(*args, **kwargs) + + @method_decorator(cache_page(300)) + def retrieve(self, *args, **kwargs): + return super(TagViewSet, self).retrieve(*args, **kwargs) + + +#--caching ends--# + """ https://arieldomino-jargontarget-8000.codio.io/api/v1/tags/1/ """ \ No newline at end of file From ead663e0eca0638c0633ee7308c7f4d61139d144 Mon Sep 17 00:00:00 2001 From: distance Date: Thu, 23 May 2024 20:44:33 +0200 Subject: [PATCH 32/46] Finish throttling --- blango/settings.py | 22 ++++++++++++++++++++++ blog/api/throttling.py | 18 ++++++++++++++++++ db.sqlite3 | Bin 217088 -> 0 bytes 3 files changed, 40 insertions(+) create mode 100644 blog/api/throttling.py delete mode 100644 db.sqlite3 diff --git a/blango/settings.py b/blango/settings.py index ab0387b3e0..da92a62030 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -162,6 +162,28 @@ class Dev(Configuration): "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticatedOrReadOnly" ], + + #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", + }, + + } + + + + + + } SWAGGER_SETTINGS = { 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/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index 2b00c53ef653cae9356c734326d7753b855418aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217088 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 From 3d2b020ab2bfb3d6ff80a8e6734724b42ca995bb Mon Sep 17 00:00:00 2001 From: distance Date: Thu, 23 May 2024 21:49:32 +0200 Subject: [PATCH 33/46] Finish filtering --- blango/settings.py | 5 ----- blog/api/urls.py | 3 +++ blog/api/views.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/blango/settings.py b/blango/settings.py index da92a62030..b35c36eb9c 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -181,11 +181,6 @@ class Dev(Configuration): - - - - } - SWAGGER_SETTINGS = { "SECURITY_DEFINITIONS": { "Token": {"type": "apiKey", "name": "Authorization", "in": "header"}, diff --git a/blog/api/urls.py b/blog/api/urls.py index c25d1df8df..96236507c1 100644 --- a/blog/api/urls.py +++ b/blog/api/urls.py @@ -42,6 +42,9 @@ path("auth/", include("rest_framework.urls")), path("token-auth/", views.obtain_auth_token), path("", include(router.urls)), + ##filtering to be added after router.urls + path("posts/by-time//", PostViewSet.as_view({"get": "list"}), name="posts-by-time",), + re_path( r"^swagger(?P\.json|\.yaml)$", diff --git a/blog/api/views.py b/blog/api/views.py index d8130ac7f1..3ae6f9bf16 100644 --- a/blog/api/views.py +++ b/blog/api/views.py @@ -1,3 +1,11 @@ +#querying using db models Q +from django.db.models import Q +from django.utils import timezone +from datetime import timedelta +from django.http import Http404 + + + #caching imports from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page @@ -38,6 +46,48 @@ class PostViewSet(viewsets.ModelViewSet): permission_classes = [AuthorModifyOrReadOnly | IsAdminUserForObject] queryset = Post.objects.all() + + #user based filtering + + def get_queryset(self): + if self.request.user.is_anonymous: + # published only + queryset = self.queryset.filter(published_at__lte=timezone.now()) + + elif not self.request.user.is_staff: + # allow all + queryset = self.queryset + else: + queryset = self.queryset.filter( + Q(published_at__lte=timezone.now()) | Q(author=self.request.user) + ) + + time_period_name = self.kwargs.get("period_name") + + if not time_period_name: + # no further filtering required + return queryset + + if time_period_name == "new": + return queryset.filter( + published_at__gte=timezone.now() - timedelta(hours=1) + ) + elif time_period_name == "today": + return queryset.filter( + published_at__date=timezone.now().date(), + ) + elif time_period_name == "week": + return queryset.filter(published_at__gte=timezone.now() - timedelta(days=7)) + else: + raise Http404( + f"Time period {time_period_name} is not valid, should be " + f"'new', 'today' or 'week'" + ) + +#end user filtering + +#serializer + def get_serializer_class(self): if self.action in ("list", "create"): return PostSerializer From 80b60073978645fa56f76dc08a0db0185e7bebee Mon Sep 17 00:00:00 2001 From: distance Date: Sat, 25 May 2024 09:43:45 +0200 Subject: [PATCH 34/46] Finish pagination and django filter --- blango/settings.py | 12 +++++++++++ blog/api/filters.py | 31 +++++++++++++++++++++++++++ blog/api/views.py | 33 +++++++++++++++++++++++++++-- blog/models.py | 3 +++ blog/test_post_api.py | 5 ++++- blog/test_tag_api.py | 49 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 blog/api/filters.py create mode 100644 blog/test_tag_api.py diff --git a/blango/settings.py b/blango/settings.py index b35c36eb9c..3875ed9302 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -58,6 +58,7 @@ class Dev(Configuration): 'rest_framework', 'rest_framework.authtoken', 'drf_yasg', + 'django_filters', ] @@ -176,6 +177,17 @@ class Dev(Configuration): "user_sustained": "5000/day", "user_burst": "100/minute", }, + +#pagination + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 100, + + +#extending with django filters for rest rest_framework + "DEFAULT_FILTER_BACKENDS": [ + "django_filters.rest_framework.DjangoFilterBackend", + "rest_framework.filters.OrderingFilter", + ], } 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/views.py b/blog/api/views.py index 3ae6f9bf16..9ddee50887 100644 --- a/blog/api/views.py +++ b/blog/api/views.py @@ -4,7 +4,8 @@ from datetime import timedelta from django.http import Http404 - +#adding reference to filters.py +from blog.api.filters import PostFilterSet #caching imports from django.utils.decorators import method_decorator @@ -43,6 +44,9 @@ class PostDetail(generics.RetrieveUpdateDestroyAPIView): """ class PostViewSet(viewsets.ModelViewSet): + #removing and replacing to use filters.py: filterset_fields = ["author", "tags"] + filterset_class = PostFilterSet + ordering_fields = ["published_at", "author", "title", "slug"] permission_classes = [AuthorModifyOrReadOnly | IsAdminUserForObject] queryset = Post.objects.all() @@ -103,6 +107,17 @@ def mine(self, request): if request.user.is_anonymous: raise PermissionDenied("You must be logged in to see which Posts are yours") posts = self.get_queryset().filter(author=request.user) + + + #pagination starts + page = self.paginate_queryset(posts) + + if page is not None: + serializer = PostSerializer(page, many=True, context={"request": request}) + return self.get_paginated_response(serializer.data) + + #pagination ends + serializer = PostSerializer(posts, many=True, context={"request": request}) return Response(serializer.data) @@ -140,6 +155,19 @@ class TagViewSet(viewsets.ModelViewSet): @action(methods=["get"], detail=True, name="Posts with the Tag") def posts(self, request, pk=None): tag = self.get_object() + + #paginaton starts here + + page = self.paginate_queryset(tag.posts) + if page is not None: + post_serializer = PostSerializer( + page, many=True, context={"request": request} + ) + return self.get_paginated_response(post_serializer.data) + + #pagination ends here + + post_serializer = PostSerializer( tag.posts, many=True, context={"request": request} ) @@ -161,4 +189,5 @@ def retrieve(self, *args, **kwargs): """ https://arieldomino-jargontarget-8000.codio.io/api/v1/tags/1/ -""" \ No newline at end of file +""" + diff --git a/blog/models.py b/blog/models.py index 74be1a7e0c..e495f889b1 100644 --- a/blog/models.py +++ b/blog/models.py @@ -5,6 +5,9 @@ class Tag(models.Model): value = models.TextField(max_length=100, unique=True) + + class Meta: + ordering = ["value"] def __str__(self): return self.value diff --git a/blog/test_post_api.py b/blog/test_post_api.py index 05803eebb8..1ee04a21d7 100644 --- a/blog/test_post_api.py +++ b/blog/test_post_api.py @@ -56,7 +56,10 @@ def setUp(self): def test_post_list(self): resp = self.client.get("/api/v1/posts/") - data = resp.json() + + #added to list results in json response for pagination + data = resp.json()["results"] + self.assertEqual(len(data), 2) for post_dict in data: diff --git a/blog/test_tag_api.py b/blog/test_tag_api.py new file mode 100644 index 0000000000..3d54b40ad2 --- /dev/null +++ b/blog/test_tag_api.py @@ -0,0 +1,49 @@ +from django.test import LiveServerTestCase +from requests.auth import HTTPBasicAuth +from rest_framework.test import RequestsClient + +from django.contrib.auth import get_user_model +from blog.models import Tag + + +class TagApiTestCase(LiveServerTestCase): + def setUp(self): + get_user_model().objects.create_user( + email="testuser@example.com", password="password" + ) + + self.tag_values = {"tag1", "tag2", "tag3", "tag4"} + for t in self.tag_values: + Tag.objects.create(value=t) + self.client = RequestsClient() + + def test_tag_list(self): + resp = self.client.get(self.live_server_url + "/api/v1/tags/") + self.assertEqual(resp.status_code, 200) + + #updated resonse Json to return results + data = resp.json()["results"] + + self.assertEqual(len(data), 4) + self.assertEqual(self.tag_values, {t["value"] for t in data}) + + def test_tag_create_basic_auth(self): + self.client.auth = HTTPBasicAuth("testuser@example.com", "password") + resp = self.client.post( + self.live_server_url + "/api/v1/tags/", {"value": "tag5"} + ) + self.assertEqual(resp.status_code, 201) + self.assertEqual(Tag.objects.all().count(), 5) + + def test_tag_create_token_auth(self): + token_resp = self.client.post( + self.live_server_url + "/api/v1/token-auth/", + {"username": "testuser@example.com", "password": "password"}, + ) + self.client.headers["Authorization"] = "Token " + token_resp.json()["token"] + + resp = self.client.post( + self.live_server_url + "/api/v1/tags/", {"value": "tag5"} + ) + self.assertEqual(resp.status_code, 201) + self.assertEqual(Tag.objects.all().count(), 5) From 41b24f89e8481c14bb2caef1e2ba7c80af0570f0 Mon Sep 17 00:00:00 2001 From: distance Date: Sat, 25 May 2024 10:09:04 +0200 Subject: [PATCH 35/46] dbUpdate --- blango/__pycache__/settings.cpython-311.pyc | Bin 3836 -> 4316 bytes blango/__pycache__/urls.cpython-311.pyc | Bin 1272 -> 1727 bytes blango/__pycache__/wsgi.cpython-311.pyc | Bin 683 -> 795 bytes blango/settings.py | 14 ++--- .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 168 bytes blango_auth/__pycache__/admin.cpython-311.pyc | Bin 0 -> 457 bytes blango_auth/__pycache__/apps.cpython-311.pyc | Bin 0 -> 548 bytes .../__pycache__/models.cpython-311.pyc | Bin 0 -> 552 bytes blango_auth/__pycache__/views.cpython-311.pyc | Bin 0 -> 564 bytes blango_auth/migrations/0001_initial.py | 44 ++++++++++++++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 3637 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 179 bytes blog/__pycache__/admin.cpython-311.pyc | Bin 911 -> 1015 bytes blog/__pycache__/models.cpython-311.pyc | Bin 2943 -> 3900 bytes blog/__pycache__/views.cpython-311.pyc | Bin 1828 -> 2104 bytes blog/api/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 165 bytes blog/api/__pycache__/filters.cpython-311.pyc | Bin 0 -> 1480 bytes .../__pycache__/permissions.cpython-311.pyc | Bin 0 -> 1342 bytes .../__pycache__/serializers.cpython-311.pyc | Bin 0 -> 5041 bytes .../__pycache__/throttling.cpython-311.pyc | Bin 0 -> 1190 bytes blog/api/__pycache__/urls.cpython-311.pyc | Bin 0 -> 2346 bytes blog/api/__pycache__/views.cpython-311.pyc | Bin 0 -> 8224 bytes ...tions_alter_comment_created_at_and_more.py | 48 ++++++++++++++++++ ...omment_created_at_and_more.cpython-311.pyc | Bin 0 -> 2244 bytes db.sqlite3 | Bin 0 -> 217088 bytes 25 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 blango_auth/__pycache__/__init__.cpython-311.pyc create mode 100644 blango_auth/__pycache__/admin.cpython-311.pyc create mode 100644 blango_auth/__pycache__/apps.cpython-311.pyc create mode 100644 blango_auth/__pycache__/models.cpython-311.pyc create mode 100644 blango_auth/__pycache__/views.cpython-311.pyc create mode 100644 blango_auth/migrations/0001_initial.py create mode 100644 blango_auth/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 blango_auth/migrations/__pycache__/__init__.cpython-311.pyc create mode 100644 blog/api/__pycache__/__init__.cpython-311.pyc create mode 100644 blog/api/__pycache__/filters.cpython-311.pyc create mode 100644 blog/api/__pycache__/permissions.cpython-311.pyc create mode 100644 blog/api/__pycache__/serializers.cpython-311.pyc create mode 100644 blog/api/__pycache__/throttling.cpython-311.pyc create mode 100644 blog/api/__pycache__/urls.cpython-311.pyc create mode 100644 blog/api/__pycache__/views.cpython-311.pyc create mode 100644 blog/migrations/0003_alter_tag_options_alter_comment_created_at_and_more.py create mode 100644 blog/migrations/__pycache__/0003_alter_tag_options_alter_comment_created_at_and_more.cpython-311.pyc create mode 100644 db.sqlite3 diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc index 4e56f86922b0c79d60130e24b1282be104f6ce20..eaace7832e0e48f22eccffcdfa43cb29f5792bef 100644 GIT binary patch delta 1378 zcma)5&u`;I6t*2ZiPOeSvuT_D+Wy#C~e(tx*_Stju5_BZlCL#P3&Sj zLNyXuapJnNgb?bf7r3rg+_`b>F&gbZu$+SEh1DK-b~ja37KyRv^UV9+eDmhL@$1{>o z)?xeWZumFd?wS;EXS6zu-z0vsJOwFLfV7bUJ{Y1XlLiJd$PS&+s5gAdnzT9sSv3a& zjG$L8@`BNLpHaxeE%eGmA;z&7$1U1q)jUk7x1gvNfKkV=k7J*J@}ydXDRmO2)hX=L zxY5iR4YMYVquV%|gLznhMYway!V)aQJFsFhaQ8H&&VY!AdN=$bp)sNmzQrW?HP8lJBmx_aCA+HoJ3jt#eM zd7kCiUa8`hJN}Vj`__T(8&G!}h7NVRbt1BXxZiTjl74KJ{3F-#{g!2$rLyhVa>w&^ z%Qir{v>UualR%kKC_kVf03 zF|)qb9hgOy2s%cud4uptCZ6v{f<)+`_H{0v$6QdlnL*vOY~9BUmiBbhsCC-=hKr*& z#+QKiE?B>;j4~+AO_8m*aNBX+HuQtXh7G~;jg8(^wN!V(a54Rt-eM|D^YdLYwIywr zn^i^IlNz5@WVuqWX`9uuEK4#e4mjmOqpm1b>53Wu7t<&!*H`S7cPh2Aa-~Liv`Cs< zQ6(}lSiD`yRTZhBZIm}3O0_MyCeDVx^WQyxkx^*+S-3vDo1YmeuzhL(PfGoz@Q>k( zF1wQKQ&%8RsgL^!=1#Jo;TDj%0)d)b>=ovEMa20&&&@?7LXKK^ME6Du{S3w5@1kZNzh)q%;kj15G20BG{ z-}^IWF0S)O9K}udrWSvx|Mt=OXcgaHzWC$H`RF1hXBP7oL4qqV=Tz?Ujl=i{&;hdT delta 909 zcmb7CPiWIn98S`#&DN&v+H_sB{#&;?I%u6DQ-=&|n{Az2BT0+n9hSp>wN%`Ko%2u+ zEuK7xFmf3(a1L&~Kp=2V#yxY1@_E}98ukTx$ms+huPswqY?NS7><2>D+ z?O=UP62u!^-BrU4Y$hTXFv$J?3EkUe64m>GLLOXR=tXW^T$zBrT@v|lx8pw0OMnCU zfi2m(*6itp1@U%r zMiLjJhBG5(bD}aQ%7uJ-QK&hSS0*MVQY$J{)Ko>y8+2Y>DG#6Z5jcWn^ha z$%x|3oS?{9k#hy%ge^!#xgcZ}QOeEcu-Eg4SI874!SKm~EaRl#5nbAE(%!TDN%a1t zqezmUw5RNRogsX&9qAR?Ti#vPFJ+r?;V>>VIYFOWYH~}B^Q}Ngrv_SlaC`hk>Ul~} zTy64Ghy0X2op17aJs_H@e@Lge79Z9re=86%nP5z(0&Rxn=r(Z*jZ{0zxG`}Gjq$d2 z%zn8{^u*f)J!U-v7Ar?LQs!5^fnR0Sh9SWiHbnt-MNz6__^f?&B}c1iQ|@>&c=v<3 nwyMI$z0!1b79Y(AzWz*$xQl6#BJt?I*ZC zdcIzAeqKpYW|CfFX-S4&X;Dsbl^|SEK~a8MW=^X9Ep{lYiaoI)Q@_kmzlt|0CqG>e z$OG%q6q-Dn*}%LA81M|YxWH0nnW^Q)w^%^zTY^bBAiLthc0eS!k%T7SW!B(fOHVC{ z&nzh702(owg(a9<3djJ45JNF10gmE z-;sHN#p5E2#}yV2h$hJi?i1W+_+J#!xgw%-kwy0ki|z-04t}Nv?hj(Dj4U4*fJBi5 G(8&NKo2hsJ delta 268 zcmdnb`-4+sIWI340}%A>&`E1$W?*;>;=lkql<|23!$b`^IYx#Q#uSzu)+jb6hE$dm z)>Nh}mQCfCa-5zmyx-^fPx+{2ws4p$t-Nn0w35}`I#EHL9j>|s1X3NE;RxG diff --git a/blango/__pycache__/wsgi.cpython-311.pyc b/blango/__pycache__/wsgi.cpython-311.pyc index 5e946e3d09302eb242679681d1ac545a10029ca6..52e81caee044dc1e857e525fd40727f9edbe93bf 100644 GIT binary patch delta 440 zcmZ3@I-8AeIWI340}$*xY@R00G?7oDejboFogsxGiXnwDg(-(IiZPWjiwUF#1Qswu zXjC$VIfZ2zP|a$%Iz|Q}bg`zeq3cRfWlUkuVaa8UVr67tVn}6O2DE84+_osT6sBMX zO^%6E?oIAu)U3ZH;^O7#=k6cx?Cz>YDs)~ae8Ka zVnIPpW^!UlW`3R~`z`kP_>}zQ`1o5)`Ng-`Q}fC)i}LesaTTYQq@<=Lmgbb)5>C#~ zOUq0zEdpyP)&m(*#0GT7Ep8;ECjVx1Qx^b=G6HdNF_8Gc%*e?2fr*`w`&XB?o#gM|7!j!`p#R#OCbC`0OqnH^P zQW>*YKC<+w~P{#S1@W?YqH;BkB?8uPmYhj#gt!sOCTjHF)uw|FFC&` zRj<4_J+p`zXy7gO)V#9HqWrvDT*av+DXD3Rr8y;&otc6y`GB&FKwKOFBt9@RGBSQ( yVrOLezyKnI7#O)4I6?3MgW&~KGR3x3P}|J diff --git a/blango/settings.py b/blango/settings.py index 3875ed9302..2803941c7b 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -34,13 +34,13 @@ class Dev(Configuration): - 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' + # 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 diff --git a/blango_auth/__pycache__/__init__.cpython-311.pyc b/blango_auth/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..468da9eadeaad34d25afd9281fcaa2b05309677a GIT binary patch literal 168 zcmZ3^%ge<81lP?h(m?cM5CH>>P{wCAAY(d13PUi1CZpdC%vZ!joaKt(qg t^e>>I8w|=9P|*i221d3PuImiKml%X6xXjSJ$e?nCLFEDr74ZPg2LSZrcohHu literal 0 HcmV?d00001 diff --git a/blango_auth/__pycache__/apps.cpython-311.pyc b/blango_auth/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb618247283519a829c23ed6fcf11f7bd7d0d1f0 GIT binary patch literal 548 zcmZutJx{|h5IrX;D*ZqeLq!J$#w;13LI|m}z<}t2SfWT)ijz|0CLw8r%G7}$z}Ars zg!nVvsIoG#6_u$IXD3Cf;B4Q$^WD3%@1&3HGlxgUQLl?}|D@+|mJ~aZKy75S^<0vt z)U*2zrAdKOB*a|c5~a7X&9lfRb6h*-13~hsE9+QF#=fRhTbbztBR`UjwjfnVuZ zyfU#Bm8lcHNs3g_Yvvvp}v8nY;%RMsd}XADEc4NC8aEG$+g zut2Zn!ReugvefIId7b1T4im;x&ktEVNY)Ew!|d8CuI$`BiY=wjlc_+uqtfVa%nWPP z5slT3o^8r1zeP`n`vMyx_e$PR;!NE54!S#qyD(6QEiVqKBg)O13N5Wx@us%eRTO)Y lVwdJRA!G*o)Ac!nY5S_ithZ+(hA_zhdEhNoZ88wIqY9nYPHsK&7+0~p}ZsY8oL9wvN53ute zDB_=qU?JCvg6f&qN!%`l7s=S)`3FPBnnt5hn0)@u~=2@6=<+E8e**R|3Dj)o7S`$7vFpqN(O6 z-N9#S_I|aqxc-_dyrl}ASfNKW9OvU>)ah}g)(4;h4ub^rhX literal 0 HcmV?d00001 diff --git a/blango_auth/migrations/0001_initial.py b/blango_auth/migrations/0001_initial.py new file mode 100644 index 0000000000..6b3075a04c --- /dev/null +++ b/blango_auth/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.4 on 2024-05-25 08:00 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone +from django.db import migrations, models + + +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/__pycache__/0001_initial.cpython-311.pyc b/blango_auth/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ee94d85836c62b7089bc3daf867c26dc49f02c8 GIT binary patch literal 3637 zcmZ`+PfQ!x8J|D4v5g@%34wsIhlC{SWMg2%rfs%qH-!9wY|>2@=q^H<4t~#O$au!Q zH-<1(rKswm$35+$Y7aT3s=^^h4n1((<4}#X);q1Vr`((-Ql&jq{oWhfA%PB_?|Xl~ zfAjmk@6GV{o}Q!x*Ps9Ppp3gE=_fvEU(wd!^*ju|m5_vFOInfzS6W;ZT(Kh6=u)&A zTZ+k&BHfmd`WFdBaCEJ!ZS0vUNzdU9uca;|e<2m3FZuL-L1D?NSs{jNIyPmos)Mja zOD$Q!23wQYX|VCFghd=n3X+#pq*Uakh$4ZvSX%|Dh_7_5#XBo9l2H`JcDkNLAQHI4 ztE`~7kW92!Py)qwlFt;7g1aNtEu;imd=0G0D7Dk`?sI{)ULglqh8OrehSEEkLvnrZ zc|DF!m@?`|Cy|U(YdvlKwcgI?SbOv|-5wp}&tUSrvrv&y-_F1@K;(G`6^L%|J+CtO zJGC=(D7xVz_snNlysrN%YZS=}HgSxeU3V z`c6e3|DSwzdgJNIb_@skyZJ0bmG4}6w|1fK2hcB|kABG#_uBn458V_o>h3B#J25f& zk#1NnCVHI$DVv14x@}akUNs);7Pc!O7FmEjrkLm!Q)Xuh5tagNM=udQC&ro> z7gQEqu?+hWi`aF`3T0C!)oHzk2@l)*d~)|LjbfdSL5fGVyQy>!R8MqR_LQ;4Dq;j&S;T$WhHR!w*D zV>b*Y&OLF^SgdZF>vilEu7}>ar!bZV^Lf+ zOzY);LDuULW6eN_fOi(>DRPZ++56Rj^lYwTK$vitLx@U-&GW38w(Ep>Gf>sEHEO!J z=%s{(Rt{JKOaeIO2KMlgHrrXJkY5XFD_C>GB83ENp=MEAuqn21sZ}MWOzv!S;@R>MYVl}87NnWSa4$VAQQ6uT_KjeAMn&_&_1 z5DRn1Q_oTaTf9rdChTj^ph%%%Ma?Qu3?lk8H>4kE6z|LzHC!^wX6c>wDMMj)8m#CY z6Q19ydBV;?4Ho5=p@l*}#}A$hdd;d6!(wq`g}S`*S(mf22HBx-&P2kdDx26s6gFz% z>pxyKY$$F*?-vGHTo2DHoh5Z0z6uMYT9uuY}nx}hSm(b z;;^ogW4pv$A^h+rBi!KNf^C+-{zB`RSZwzoOWx$|6B>xtUDJZLalwJ-*qEudv&oL# zGwiK-htp83n_Gb?2Y@9Vz&-pZ`iJxyd`Umg-MG5UMdBO;tG&d-7Q`DJAlM zO$PB?BQx-0rr#g9@byM8aA7y{{g}U``!?|h=>7l=2B@EQ8<}B$pCY%Tz;bt@AORF?0ELyzY*akfIDf417if?A zUtvFA-p`kVeA!P|8kwx0D{hyAT=B*HUdjKmnr*R`Q=nq}oJ{JsKd@;6n z*yn6JMkI8V$5CtEc?o$?Yq_ z@Z>JXV^+4f**_{bxsfIH51e6jowH9PceqG)HUhqYf-j)3l9`J%GH3j=pFCX*&VI5t z=0Dto)!WJgZmO&Zi|b*!AE+g6>Kx+n0tFjDxecr~hkH--HZ^JVeB*qvksWPzMf!W2 z5+^CCZ+QFGclU$rc#ysFLw5RK+3Ef4Opu-N(>I}`H&xuXQ$zJpnRYhUz!vD5mA)-qZVhm<&%!fu@9 z-bDy^Vf1tS*Wr=(8}d0!Ww@yaq^ZiX49zK@_NCGA*O1Qo?O#L6`t4ss`oKTx*NmN! WV@>HW`8pY-$6oP`H%IJ>*!~9$&=l7I literal 0 HcmV?d00001 diff --git a/blango_auth/migrations/__pycache__/__init__.cpython-311.pyc b/blango_auth/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d9b74c928a84b9a054cd8f5334c6794c18e1f82 GIT binary patch literal 179 zcmZ3^%ge<81lP?h(m?cM5CH>>P{wCAAY(d13PUi1CZpd2KczG$)vkyYXe`L$Vtyd;ftit!@dE>lC}IYR0RXODEl~gf literal 0 HcmV?d00001 diff --git a/blog/__pycache__/admin.cpython-311.pyc b/blog/__pycache__/admin.cpython-311.pyc index a353f6620a404e45e8626b62bf7678af59f255d7..11db98946ed2b3ec3f658811e1f4042c81d7890e 100644 GIT binary patch delta 386 zcmeBY|IW_4oR^o20SI~=EYc=T!Av0Gr^!7zk;zcF2;`SrT)z1!sX2}*xtV!Dg$tNOl!ZVF!Ky`o ztX~{9x%nxjIjMF>XpD>X1i^C>2KczG$)vibq$YliL;tfFJ l12ZEd;|&Id3#jM=7XzdA1qLl#XmT=hG4}-q86X1N0RU7^O&ID$qpY5LNAo27y`*J@$}859!TRR*LqTQ*Tf><0FD6oxe-pTrFa$p`o4RM(R%X(h z@`v zJ`~!jeAS`EqUp@!8n=gv*fak9E(w{m9JXPYZXLkF(Qmu9aj846;*X+)o&#WO%9YVI zK5p=|0=8?sFS(XI;i7RdS`X+ers;arH0L$6pHIedyXA)vFR=TSLDI|B&8!W{!RVX)qmA1LZgkHyWtg5JHQcNg{Uh5j9h zmz#IES78^=_KyW2UXCI!y2GA^YuPg)%2%^p;fJOm!{FiiVG6ipxxiT!L?SYKEVgO_ zFhZqFvx3-=6E>99m-#hBh5UqlrPrCAwK5(AF6?AXV%e5wB@EtI$(WhW5bK_=k2@I; zD5iIR&H~ohv^DOTX#yppr?Q@7W}L6h6e09=@E-K2hlg>awiL@yDVS_j`na|QNWOBQ zk}9yHZR}?$%3oxENY`6Vg*S$B1k~WJvdeN(+e{QHd$uZjij_SqFSlq<+6q;JTUCR_ zszLUPd_hL~_;OVpKL^3O%dUk!jX*f$RS^OvvhPBFbzA@j_(C0Z)vn?8h-H8GV>SA6Wc3khUx=<%=M!-Ui8_`oWMZp%1 z8VC}?6#!ouzH=+l-<$A55Wp#i(zj9c4nUN4Awi$Zj@fB<$|7dUqZmlq!)DbM{!O;1 zUhIk?M;$Na(>=3V-@`iyjWFGR*mKp?o=Sz9p{<&sV$BfyBD^f4I0&B&-K3G};`s>S zH-v-mb%>X9M1+>15eW)N_RFpZ#tu_wB5hl~GFMs{0(}i1yNcS5$`m%}InKRKfEDf= zZW_L&F@3~)#1S4OM-A)|_q_N+26+pc=D3}5UDFI=>`PhKqGzY5 z?j=(XbM+QH?qaT>6I9nf>?~Frg^C;S-}HXHUs@H^8wZKh;gKA;b<`b#O!{TFGFHL? z7)V<-InF741N73*5Q4fF$I{N3iRniQ0$y-O08X?Ktu_BaQEUBCEyk`s8TkG4cp)`i z&}O!@nW8qc(6=3l=G%*r=vJhu7-?GQ#s1REW8*jrOT7!9iW}wZdHCCRW3Ud2A~4`o zgL|QVH0zM8ZC#_cfrlA#&+%*}GSZ>&(4Ag|eEVX@E}rin3+q`_8|jP5JX&-%E)6`q Nxf{s#|A!G+{vUUBrb+++ delta 953 zcmZWn%}*0i5Z~SIN4x#h0@4QC57^MCMew;mcfP{ltjfqDO7z`#xO~iREh~jMKmpAi!GyC3e-iyR;OXx{3 zsB&oEpQb05q-v;@uT@X1$;5w!(*o6BSN$HwqV4o^(5}P>2wxLcc#c~`Z&+R;o?BdA zCL-2It|wQR`b3ag^}aHdbub=#r3 zzTn65-o3zw^bR}TBKI~NU0zuC#&$5`vYG7W8S$?G)zLn7}F@iDo2~y z`X~d|rVcVL>;Z(uKOa0$n_*W?!j>n_2Oy&Ko`z5;!AF1mal{@XF{7 z%SSNpnK`l_6R{>WM1d_a787goA&H_yL2^vp?fb z%+MwV?4(&O{`W0en$4JYF%TJaBe=@Q&2 z+*7LxBePl%jNnplUE6xezioba2TK;Im_~*U<97Dm?T;KJLSt!>k8gAT!|m?n^cR-@ RdO$|tacB(Qg?v(7q~Fnm;Bx=~ diff --git a/blog/__pycache__/views.cpython-311.pyc b/blog/__pycache__/views.cpython-311.pyc index d2fd87313a3b16423f2c59a4f929783ba102013b..7511c9c2859b2a3295e148d749abd3d4648f4cf3 100644 GIT binary patch delta 446 zcmZ3&w?lw$IWI340}xEVXrA_$bt0bxW6VVLaIO}HDBcv#Ud6B{eXR{;hdq0Ny@N0=BzC(E(kt50SG83hF_K$;ndKU)Eb=?p21 zDNH#Gxs0`pj0{W+HHE$)=8`oz5Se7y{yq9RqGj$16gt|5+^Ohp1f4p^iZ zWRQYFkswHxEj_g)KC_@m6Ufm4;$m$e(ZFznN$mnEy1^;hAu}O;O3nh?D`JKhIgPGx z8eL#ADiQ{&_tWIN#gmc+wzzaMCtHjf*u0etU|Fz5zc_4i^HWN5QtgWLfjU55D_%Oe ijjh0zi_wAc0|OE%CCbS3fdNkFF)IoGI7={tCg;SC0>-Y1Khq~CG2URbm~73Y z%zT$yaB~LJ5hh07$-1ofnEf<)C;w+lQ(VbVBn&j8NDxT;;;_lhPbtkwwJXvDav6cR dc-rJ{_5wX7MhC_Z3`iuGC?nGc2228M7yxW}Cv5-# diff --git a/blog/api/__pycache__/__init__.cpython-311.pyc b/blog/api/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd8186ba5e0c0394a90ada91a786f98a30eba483 GIT binary patch literal 165 zcmZ3^%ge<81pP@)X(0MBh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%fi_zCbT%U zs5r*gHO3{sJTE6dF{L;rDJL;6Js&~kr^h4~WX8nDXXa&=#K-FuRQ}?y$<0qG%}KQ@ aVg(umvZ|OLNPJ*sWMurn03(W+fnor)3MWng literal 0 HcmV?d00001 diff --git a/blog/api/__pycache__/filters.cpython-311.pyc b/blog/api/__pycache__/filters.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..974a585a0683e8b13c117b9fb357bbcb217e8498 GIT binary patch literal 1480 zcma)+&rcIU6vtp^43$OiWBnJd~6ZC*Rv`p=CWdyYu$VzR$d!`MzmC#bO45_Wi^2!h3~~ zpE&3?t>d(pz&R$AP{}4`DJM(l%eGQhbE-sSvP!7(j!>1!JKZ_t1hRdF9<-cJwGEQd zf8#JCi8yDjXBN0sW_u3bg*WQD<9b^i+<$aawSU3zm@tx)D9On(Vakr$1*t@3s(e$g zeNGEe8r4IT?o*M=ToF1z2Se1rHOc^LC`1ijqoP2?LX_c)6$h~rCo%{GJ!rWhngoG{ zMGSRVDcK(58_W~YYHialx!a7ImM4^=$Lgc&-B;-)%VX&k?vyi%FbXARQ?mk+iXq!^ zc579Wy{&Sg+14hrg#sz-!`J4%a74_idD{**O;)x_wiqmJITg<;Rowd66F-t(#z1

2m#YmqT zb8F1AgdQwdsGe1Hvl(3snEtL!Qy8XMc4*CJ;73jKP0g}V57Xp$V1x!iP(t^Wa=(x^ zYJV}m_$dF8rDb2PvhBr$bZ zY{b*e(Mg|41Cu}|flNX_Sp6?1Dr?g%5;m|PZ5kF7iUva9*0Y(2h|?V`v)NIf<3AR^ z14ayO+D>uKKQV4_dk YTl!52wh}f+tBusm83xY(5%u@;7w^1$xBvhE literal 0 HcmV?d00001 diff --git a/blog/api/__pycache__/permissions.cpython-311.pyc b/blog/api/__pycache__/permissions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..302f1d162a065bb03155f52f3d9357f51710b2a8 GIT binary patch literal 1342 zcma)5%}X0W6rb6Tm>M;$T1DD|P%1=8l9n9oK%vl6Z;^QN;jBX~AO5a*8ZhcM&^e=Si&_wtZxxEG8%`5xj%4GLayL%zMU4 z?xI6y9b)A-()6WikaW@B$OUJEtH@U5SexLc@6u3Hwip670I-ctVx7CApWamNJt@b;bJEqj_xN|bXLsv>ecb#J zPn4sH8Zc1`YK!W6(I}X@&Rcc8;E<9n-rID2wPe`7h28@05u%;2(VGIqVIGM3*?oZR z;8&Ftb+C#W!S@%gIaq=G&%AU$XJe#b6-9@q9Gdo8b!A$_Es9ke2iQk$Jt}B04}s>y zE^FcJ90KDwV8M*w6pX1d?>IKS0Wo;gVmfn;e4fTY4es4$LdHGHG=Oi zyi$n>@1QWrdY;17uRC(mrC@`zn0i}bi|*aQQ8mZ zh<>tn096@de1ZnbSD$J`#V{$2Z~(JAeku%Sp|wN8P*)=yBrA6woeINQ6RTJC1x4^T A#Q*>R literal 0 HcmV?d00001 diff --git a/blog/api/__pycache__/serializers.cpython-311.pyc b/blog/api/__pycache__/serializers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..674d54945ed7864605ad86fcf4b4f77b98c01bb9 GIT binary patch literal 5041 zcmb7IU2Gf25#Iabk4TZCPqyWM%JNU?A6aT81F?-Hs()nHwo=q~3NtDWXWdDqd?Mw& zlV!VfArt}121b*I7EaM5Ad13uU_TT^fu==)Hg9<(1r{O}2w3fC zk&4?a@8B59V5%L6Xw`8MSDg;vDS4een zsR&3#Um+FaQe7Yw1F5)Yqs?MBmx_Z__bYTIxKsk9l4h(;L(=HELNclUpn;6QVzxW%V0Sc}Pr>6%3LUxm^M5 zijPE?DY)H|l?(~2$}EAJt~5;sbb8XO+XFgHDJ##w%Kb*jDuxJJq?c+?%iv|x(P8ji z(>ZE8T3(+sS=7?Ks$$ORIcp*lViAoqHH~#?+FZd{uuQ1OHSN}dZece~qhT=46kwVb z3r*)t%dkHstN0l&Po6$8iI(lj%V#Ig6c+PVK{xEl82`Vh88% zG8qR+W1uGrx4j$4Qu9~u=`AVs)M$lYr4at;W)PuUflPcV(I^JWbK!3Qr*+{?Y1ZSxVm%cffy+IC+bvl| zBxcBvp2}_QVGbDuAQNhXOvDJoVn1qQlkba4kB#^zC|LXDMHHDv2Z2#F1L!$kKR2mXzTJ=_JpwZEXXF`yFUCu&y|P zSXL`Qz8ZsuXezUwEGeU7ca%ra@}$}NRB8*B5kRdpi%!6c+4c4G8M2`~4YlcX1AQ0} zA8A8;+=v1SyI2B)un%{p+}5oH^D0I-+gg~>cvNEYoSwhSBDWSyde=4`mV}_AEkM-K z42U#2izZ;q8HpZ-2aDd$nTu{@j9Z1m&4qbaOJH7`{(_+M=N0|M;K4WGD{63*+)-Fq z({#&%F#0M7z&PPM0NsbLZE)id90JAxxNTI{0yj5UZhsZ(yEjCA&lQ-y@0phFn1+jd z%n(?(aAVe-a%?8S*qQ9;GdA0L{_eaKl}^F8o+@UwO4Ub zd>k0CvBO5rw_p~56oF6Sl!qFSCFpGu+^hcv@(^~qA}K?yvjqLF>{$#57KL)F?j|qT z_o)!vQ4DcgQ}PNKAP$acy*SN8j*w|_AECPh9HEycdSd)(NJZh-kOF=z1@#N$IwX%* z$RfETT_=k|Mw!S66r(f6(cbHViAi&ZP2Y>9b9OqHhqs&o=|_CVbRgAO_t$QGD^Shd zHJD-*7EQ{cR{?itDTQ68>uq#iI8HHT7a8e(}YZJ=;D$QAr+X5FrX_g-7b~p7$@jccHRnq&xz|y{hri zT70z9ay2CA@Z!Y_8~2i*yfG${&&5MyL(=ER1fUa{j%?>N$hAj^*RT!AQMl~^AikS5 zL>aT4PU3DT*ed{d85*#B-E6iSrr3|u0hJ!2S8;iKAM#@X*6z~vAhoJ)`tL*^{ zLD~{DH;R|zAaWdTyAOyz8}%L)a|MW(ZD}P%x%PQIi)~0o;ocyN+<{qSJHae+2WFA& z40pm&U0`-3Xuz_H!~JQ?{NR4OLG~z@$h2Tc??$ppiQEygA(%ax_2Le6hHrNMDnLF+ z4}sPx&j#AT7=Z7q$6;N5DLf=au?(-B#b}Er_a&!AkK+LaIu=QA_b#`{`Fe{&vF#{y zUnojv$PeDWFBj!(9G+m5e(p?#2Yn769M+2==SWfht?+Z(B>H<3u6v$+75?MCdQ=s z!aP=u+01<=Mq3-b`EOeR67qqqj^ne!co$(X6oG`1zyvuVxUh9So3w@pZ!$B-Gx z5m;@r!~DX)Hk^j)wmN@1a4< literal 0 HcmV?d00001 diff --git a/blog/api/__pycache__/throttling.cpython-311.pyc b/blog/api/__pycache__/throttling.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..312a27eac7086231a1149af9a5a002111f0ab9b6 GIT binary patch literal 1190 zcmcIi&ubGw6n?Xtq}$lEiMG-}Jb5u7{R4^!R*Stfg4N5iFl;;17~Jf{nO#b6MZ}Z$ z{s&6&p9%C*_Ug%7B%XS5-pfyuAmrd|^3B`#y?yh&_vTxvR0O*oetC8JkpcX`m(j`( zjs9nAT!H|>B4{(8wH@DK;J`}|&LjE7FE0i zxz#zYAc{v&zw;B1s5@EtS*+sMVItqW(<(_KnU&ufsi!?BLpikB;%N2n+U*h~_)LKB z2=aJ@;9z>na6n?wk79Tk@L0^X@vF~=O5BA8Zvov zqB`;l`grQ??(^LM<(c4cKX{?e;z)(U1jkVrH`QcQ%^>Wwf@DxqD{k&|&UL{iAi#O_ z?^(DQKR0W&i+z0b`Y3A2sw-IaUaCzpuDD1QAEU3KJx_ zRZElbQ*AHLnbPlfCWF>42ImlH-qd2MhI0NrfLrg$;MP6WHTwl#ZF6m*=Q74_;Gnzc p*Yg;A(1UsC)*k;t_wNa3L#r4%3*GHU7+S^9A@_f>j`LIh{s5BF3SIyJ literal 0 HcmV?d00001 diff --git a/blog/api/__pycache__/urls.cpython-311.pyc b/blog/api/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9da657a97771a7e4e0878713132480432de7c5d0 GIT binary patch literal 2346 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 literal 0 HcmV?d00001 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/__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 0000000000000000000000000000000000000000..77ed6504a8a55a884accf6912f7a234e3aac2001 GIT binary patch literal 2244 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 Date: Sun, 26 May 2024 13:45:40 +0200 Subject: [PATCH 36/46] Finish simplejwt --- blango/__pycache__/settings.cpython-311.pyc | Bin 4316 -> 4652 bytes blango/settings.py | 379 ++++++++++---------- blog/api/__pycache__/urls.cpython-311.pyc | Bin 2346 -> 2693 bytes blog/api/urls.py | 9 + 4 files changed, 206 insertions(+), 182 deletions(-) diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc index eaace7832e0e48f22eccffcdfa43cb29f5792bef..aaafbbe647100d920bffe809d5577bd7e3c237ca 100644 GIT binary patch delta 1441 zcmZ{kOK%%h6oBuYhdtxh_%U`I+i~L5v1`(prfm?Fcr-+=VAVs3ef-a~8C}P8kUjPoO#K>Y1#2*+0tE@QVJd_|9-}B8qk9*F! zN49@UJWqO_x!n$e&lf|*wfV>sPnfPeNoOZ)>vqLX2~e=rZ0mv|P{NQr0sAKe1l9f( zOUTzKM~@-`QzRMjyX{-lA-1aPDyX$qS^u40bcn|>f34^NM%@Cg!YB^l6(`sf7chz& z#RGQS+ZCyT$#<#Z#kYuWpGd%=_`#{#!L{imq(XrKH+nt0v`RtR;VM)KfLD>h2Y!qS zSWzJFFjcAqAqXLi3PN9xVdPd2`m40UDItg`eGpZ`SiT=6f)a(85`zI{0OCp+|VP zAAR=dS83!IX=GnYzpz&Jg*4HgHa*0Do3>|68*%&Exr1PMd$c2`+H+{=%d{6v2Z;@J zxbQ*$K!*z%*1q3}CeUKUlN~N<3R_ahW|9-M=gp+xN5cs)waoE?=^}EXeGAj|&rl;c z@YJ~<#6p*haO}XFGzp&$odIhP`%Td;I!t1Lm3+2#!Az3yc!!g*Wzlbht{8G``=fn% zYt-af*<%t5Ebnt*WQ4c_9eKrM89XiYl%=!E@>%8fkrH$d**aXzOp#!$J!kkr?Sdf% z+S5nC;oQXL{V}|ci$tzl!$@B4a0z28n#O6EHf|KVJq7moCgx2$Ob0)Lj#GF@G--qq zMg%!*iu?u3iSUwBkQ{ z!MAZ4q1bk%6HMXZ1fH|N@?pG}e6+N?CtsF~L;psKy(2)mrCR?`8i$^)XSGpZt5!C& z-Y=r5@m9F)`1<;t&8bI5hVDEnRWFM7WJjiiks1N{J^`gIeJc?1JD{5r{(U zPyii_E`|6Z>rzJrRwotn>1S7{_d6{{)c;)mDti`dk2=(efapX!1f(PC#Bh~y)iuoG z7@)Ql(1uY$Iusa0&>B6W7&~?-qGud*#{>i7xXTQ7K_bkV01J}1%K|%ODrCA#JECL3 zl#_&vV*_(iaEUo-6sBO>$w1bbh8ZV|a|RR59#NRq>cUrrhUbD5ow_UwCX!no*5FQIObNr=Jr*!CTXgQB+Nj5z1 zUDoEk*R&KJ9&c!jQN{Zz`b4-^ne%>#eRBM{E>rIt^!69Gf7O?M(U(s2;6;GiGn=hF=FN1b38((KA?Vb=HTv0vNE<-{B-uYk>^Az+GijrX_ITZ zqH+>Ih{r{@!V4t5Hf9!9qnN(^if?5PZk<>aE(unQlK_GgS8-#OL=$7{78e*gtAR4I zZk*Aq`dOuMs-*OJam=!OnIyCBsvo!AimxZ!@+qQmcNvQ}vv{Bw$-F*hwx6$#S zj#-+^VoB!YC9uRdG3=7eBhn%ou7(U?km^ zwIK7i3&HIo+-lJK?}x9^tHmylHc*^Mo2@~2tHLHyqj$HxJCWWCb#0_*Q7_!6}wVJ-gx^Oz%x diff --git a/blango/settings.py b/blango/settings.py index 2803941c7b..9b3960c7ec 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -11,191 +11,206 @@ import os from pathlib import Path +from datetime import timedelta + from configurations import Configuration from configurations import values -class Dev(Configuration): +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-&!=9y436&^-bc$qia-mxngyf&xx)@ct)8lu@)=qxg_07-=z01w' - # SECURITY WARNING: don't run with debug turned on in production! - DEBUG = True - ALLOWED_HOSTS = ['*'] - - - AUTH_USER_MODEL = "blango_auth.User" - - - - # 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', - ] - - - 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', - }, - ] - - - # 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" - - REST_FRAMEWORK = { - "DEFAULT_AUTHENTICATION_CLASSES": [ - "rest_framework.authentication.BasicAuthentication", - "rest_framework.authentication.SessionAuthentication", - "rest_framework.authentication.TokenAuthentication", - ], - - "DEFAULT_PERMISSION_CLASSES": [ - "rest_framework.permissions.IsAuthenticatedOrReadOnly" - ], - - #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, - - -#extending with django filters for rest rest_framework - "DEFAULT_FILTER_BACKENDS": [ - "django_filters.rest_framework.DjangoFilterBackend", - "rest_framework.filters.OrderingFilter", - ], - - } - - - -SWAGGER_SETTINGS = { - "SECURITY_DEFINITIONS": { - "Token": {"type": "apiKey", "name": "Authorization", "in": "header"}, - "Basic": {"type": "basic"}, + 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-&!=9y436&^-bc$qia-mxngyf&xx)@ct)8lu@)=qxg_07-=z01w' + # SECURITY WARNING: don't run with debug turned on in production! + + DEBUG = True + + ALLOWED_HOSTS = ['*', 'localhost'] + + + AUTH_USER_MODEL = "blango_auth.User" + + + + #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', + ] + + + 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', + }, + ] + + + # 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" + + REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.TokenAuthentication", + ], + + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticatedOrReadOnly" + ], + + #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, + + + #extending with django filters for rest rest_framework + "DEFAULT_FILTER_BACKENDS": [ + "django_filters.rest_framework.DjangoFilterBackend", + "rest_framework.filters.OrderingFilter", + ], + + #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" + ], + + } -} \ No newline at end of file + + + + SWAGGER_SETTINGS = { + "SECURITY_DEFINITIONS": { + "Token": {"type": "apiKey", "name": "Authorization", "in": "header"}, + "Basic": {"type": "basic"}, + } + } \ No newline at end of file diff --git a/blog/api/__pycache__/urls.cpython-311.pyc b/blog/api/__pycache__/urls.cpython-311.pyc index 9da657a97771a7e4e0878713132480432de7c5d0..93656a421a685a5c13eb7934b7a08044260f9603 100644 GIT binary patch delta 915 zcmZuvJ#W)c6n)QjKFCKC$FUQa=Bq6wK_yYCDpY~gfr@0KrHH`;A@E*G+@>i`M5Ig| z03jrX;;}-CkVqxSg37>;U~rHJl&Oe60OAjD9VbIVuKlij?>*YK4h=ml1O6|z; zXF*29L&F^ghOq3wXgEwE;-%u`5Y#a7K^D}g9>kHy{_aeN1p!!g>|#ZRxsS>n41HL3 z47x6uIMmruj|51N@ZHe30uiWNy*R?O=fLSYBH?kl6GX&!ERnjAak3Jl*7lf0Au~kQ z(@e@SOJ~%$o@Ekj(6$|$o3uUP#31uFA`zd8NLZ>=QWJWT$!sK{64mI)DoK&~3Lf`K z7N+R1WRb?&NqDgwoLTqM1Z8Oo1(VA_NdaXIk@SExc5WmyN!oR{{0J#B;dW5wiXLL? zX{ImStL@Q)JC7Qr%E4BtqTR31W;gwZ_9rKjBXCynf5^ekl%Gjbd3VRcW)P?G@{l>ft^GZ1x#!P}jWh(KMg_ z(-HQ9OQg+MDe%^L1IP87&X=BbsI7me{zd=he4TJ2yp0XC_5c0bhwz5}&?TPLTo`Yj zbMB+fSCYXm8~mclFZcMY$!CqV+Xk2Kae0%={}>?RhP-0P8B<>A$*ZQk`t_>86?$C3 a7Pf)r%_APF-^UhC4XgPDJpy2i%< delta 615 zcmZuuJ4*vW5Z<}G9~YCm+~sK=zDSw~qDZjtCs+s;*Qv9H9BL#11uGjZ(uHjV2?W6e zG+n^nQt$`Z3syE_EjX9tiVJ?n47=Yq-^}jqwf3l#2C^Jvgw5{G(X*Ga4{KvXo-Teq zrB2DrohXI^OkzCPC*jGiVus4qUcB?7rWgiztS^yBF#)itSY#OF%B)D;07I0crX~uI zI1CTvzKa@AOi%+uCq2ydJ`hha!I<@t;xQ98hY_5^=rHE&fC~4UNxP<(qLWq`s?K(I z4C7rFYD3*=HqzW05161VVN&#@q9?^8W=4EL)^CwqHM8PYDsN|Kr{f z&EAhZF3j_EZ=TMz%8(5UGnV2%7Qh9vw;(O5zMJ4W5!NHL_Qb2N e4gOKXp8|LnZ-Iw+Mu|$_OabXCWPQyws`~?4xNJoL diff --git a/blog/api/urls.py b/blog/api/urls.py index 96236507c1..0cb862ea2c 100644 --- a/blog/api/urls.py +++ b/blog/api/urls.py @@ -8,6 +8,11 @@ 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 @@ -41,6 +46,10 @@ urlpatterns += [ path("auth/", include("rest_framework.urls")), path("token-auth/", views.obtain_auth_token), +#adding JWT token urls + path("jwt/", TokenObtainPairView.as_view(), name="jwt_obtain_pair"), + path("jwt/refresh/", TokenRefreshView.as_view(), name="jwt_refresh"), + path("", include(router.urls)), ##filtering to be added after router.urls path("posts/by-time//", PostViewSet.as_view({"get": "list"}), name="posts-by-time",), From 010daafa91bbdc54312b9122953fcacc76995903 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 15:03:20 +0200 Subject: [PATCH 37/46] Finish django-versatileimagefield --- blango/settings.py | 5 +++++ blango/urls.py | 5 ++++- blog/api/serializers.py | 27 ++++++++++++++++++++++++--- blog/models.py | 10 ++++++++++ templates/blog/index.html | 8 ++++++++ templates/blog/post-detail.html | 13 +++++++++++++ 6 files changed, 64 insertions(+), 4 deletions(-) diff --git a/blango/settings.py b/blango/settings.py index 9b3960c7ec..b3cf806f59 100644 --- a/blango/settings.py +++ b/blango/settings.py @@ -64,6 +64,7 @@ class Dev(Configuration): 'rest_framework.authtoken', 'drf_yasg', 'django_filters', + 'versatileimagefield', ] @@ -151,6 +152,10 @@ class Dev(Configuration): STATIC_URL = '/static/' + MEDIA_ROOT = BASE_DIR / "media" + MEDIA_URL = "/media/" + + # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field diff --git a/blango/urls.py b/blango/urls.py index 8a2b6a84a1..f77e45e1be 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -14,6 +14,8 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin +from django.conf.urls.static import static + from django.urls import path, include #import debug_toolbar import blog.views @@ -34,5 +36,6 @@ if settings.DEBUG: urlpatterns += [ path("__debug__/", include(debug_toolbar.urls)), - ] + ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + """ diff --git a/blog/api/serializers.py b/blog/api/serializers.py index d82951dba7..5e5fa98d8e 100644 --- a/blog/api/serializers.py +++ b/blog/api/serializers.py @@ -2,6 +2,7 @@ from blog.models import Post, Tag, Comment from blango_auth.models import User +from versatileimagefield.serializers import VersatileImageFieldSerializer @@ -35,9 +36,20 @@ class PostSerializer(serializers.ModelSerializer): 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__" + #fields = "__all__" + exclude = ["ppoi"] + readonly = ["modified_at", "created_at"] @@ -60,9 +72,19 @@ class Meta: 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: @@ -74,5 +96,4 @@ def update(self, instance, validated_data): comment.content_object = instance comment.save() - return instance - + return instance \ No newline at end of file diff --git a/blog/models.py b/blog/models.py index e495f889b1..477782627b 100644 --- a/blog/models.py +++ b/blog/models.py @@ -3,6 +3,12 @@ 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) @@ -35,9 +41,13 @@ class Post(models.Model): 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): diff --git a/templates/blog/index.html b/templates/blog/index.html index 78294def4f..6076722d7d 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -9,6 +9,12 @@

Blog Posts

{% row "border-bottom" %}

{{ post.title }}

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

{{ post.summary }}

({{ post.content|wordcount }} words)Read More @@ -19,3 +25,5 @@

{{ post.title }}

{% endfor %} {% endblock %} + + diff --git a/templates/blog/post-detail.html b/templates/blog/post-detail.html index 06e9066e3b..ca12a3424b 100644 --- a/templates/blog/post-detail.html +++ b/templates/blog/post-detail.html @@ -1,6 +1,8 @@ {% extends "base.html" %} {% load blog_extras %} {% block content %} + +

{{ post.title }}

{% row %} @@ -9,6 +11,17 @@

{{ post.title }}

{% endrow %} +{% row %} +{% if post.hero_image %} + {% row %} + {% col %} + + {% endcol %} + {% endrow %} +{% endif %} +{% endrow %} + + {% row %}
{{ post.content|safe }}
From 9dda5edbb7f905300c9e7fc3f61e4a481e6454f5 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 16:47:58 +0200 Subject: [PATCH 38/46] Finish JavaScript intro and fundamentals --- blango/urls.py | 3 +++ blog/static/blog/blog.js | 9 +++++++++ blog/views.py | 5 ++++- templates/blog/post-table.html | 5 +++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 blog/static/blog/blog.js create mode 100644 templates/blog/post-table.html diff --git a/blango/urls.py b/blango/urls.py index f77e45e1be..589c88e692 100644 --- a/blango/urls.py +++ b/blango/urls.py @@ -29,6 +29,9 @@ path("accounts/", include("django.contrib.auth.urls")), path("accounts/profile/", blango_auth.views.profile, name="profile"), path("api/v1/", include("blog.api.urls")), + + path("post-table/", blog.views.post_table, name="blog-post-table"), + ] diff --git a/blog/static/blog/blog.js b/blog/static/blog/blog.js new file mode 100644 index 0000000000..1cbf243baa --- /dev/null +++ b/blog/static/blog/blog.js @@ -0,0 +1,9 @@ +const theNumber = 1 +let yourName = 'Ben' + +if (theNumber === 1) { + let yourName = 'Leo' + alert(yourName) +} + +alert(yourName) diff --git a/blog/views.py b/blog/views.py index cb6821a4f2..647c2bd638 100644 --- a/blog/views.py +++ b/blog/views.py @@ -43,4 +43,7 @@ def post_detail(request, slug): def get_ip(request): from django.http import HttpResponse - return HttpResponse(request.META['REMOTE_ADDR']) \ No newline at end of file + return HttpResponse(request.META['REMOTE_ADDR']) + +def post_table(request): + return render(request, "blog/post-table.html") \ No newline at end of file diff --git a/templates/blog/post-table.html b/templates/blog/post-table.html new file mode 100644 index 0000000000..45c1d3f725 --- /dev/null +++ b/templates/blog/post-table.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} +{% load static %} +{% block content %} + +{% endblock %} \ No newline at end of file From 982d67922991eeef05216a8ab0b593a6109800a9 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 17:03:02 +0200 Subject: [PATCH 39/46] update db --- blango/__pycache__/settings.cpython-311.pyc | Bin 4652 -> 4741 bytes blango/__pycache__/urls.cpython-311.pyc | Bin 1727 -> 1911 bytes blog/__pycache__/models.cpython-311.pyc | Bin 3900 -> 4211 bytes blog/__pycache__/views.cpython-311.pyc | Bin 2104 -> 2290 bytes .../__pycache__/serializers.cpython-311.pyc | Bin 5041 -> 5436 bytes .../0004_post_hero_image_post_ppoi.py | 24 ++++++++++++++++++ ..._post_hero_image_post_ppoi.cpython-311.pyc | Bin 0 -> 1205 bytes db.sqlite3 | Bin 217088 -> 221184 bytes 8 files changed, 24 insertions(+) create mode 100644 blog/migrations/0004_post_hero_image_post_ppoi.py create mode 100644 blog/migrations/__pycache__/0004_post_hero_image_post_ppoi.cpython-311.pyc diff --git a/blango/__pycache__/settings.cpython-311.pyc b/blango/__pycache__/settings.cpython-311.pyc index aaafbbe647100d920bffe809d5577bd7e3c237ca..b51fed26196874287fada269fd0a1a16c078f4fc 100644 GIT binary patch delta 456 zcmZ3Z(yGe4oR^o20SGEpg3~r_MKwh&MLk7h4oiw=idKqticUHcP)-*pr#FWqN-0G@ z#URBn#VEyi4s(i0ifJZeirE~dOvWhX6t-XnP4ipKE~#ZzPAOT5dFlDOnR&&j$&)*n z;~DukzhLHL6ul)}mReMtSdy8OnwguJo|=}Knv=5m8_RSiMw!ih>}rhsw^(yiQ!*2) z*!98m=FJ?lnYa~z-YQZA5lWLIx!*EsOm^q-mNfPO4o|%H(Rn Y0!J@qMxcq<$qwC*V3{vCo=I0!o!MzP5@ z`~`yiVr(B6U_^u82bIZt`R_4(;F-KeU?*ebIWI340}zCH2dABz$ScV>Z=$-q79&FnV+uMhZVxQw6{V2*M38 zWlIr4u}BzhfH1lN=^RlaDXhT^nj)JgFg7!BGTvea(-T#UTINIu^z;PA{L;vx45CQAc-PY zphywh0tJD=z));Dc^#{UpwvbZPylVoK{VM}G7SfFaklER+Kp2Y)_0f8(&2+hEd!m);P88ZXJY9NLH zuvuIn)f49^bF!px0|i)9cqS__%A2#K@TPJi86yDGl){JA7=Ex`0ia$Pu5|V&!4#HY z22H`urx=@=CWo^uWYQFxEWs)-%$<^zn3tZfS6Y-)T*Lx2vxs$a25Tw{2aq}WE^9EC p6p+CP#KoMGt=Kdsm#|$H`@qk^&(y&EL5!7=Lmp diff --git a/blog/__pycache__/models.cpython-311.pyc b/blog/__pycache__/models.cpython-311.pyc index 3c0d0960b1583350bc731d09ca97f7c795c29afd..4bcf19d77a8992e000abdb17aeda5c5a4fd56093 100644 GIT binary patch delta 1382 zcmZux&usF0KlLH5&a*0N&dh98HXw^e*6@LIyFGk`Jje4m12cV!Jp>jaX+l`@+j6L7JdEeXj zX5M?V_e#6v%&l}w;?Ia5n-kUR%6gb)okYi_JxiN4;3jig6K7cSr9we;`I=dFT8U@$3 z9>spdL+=PmL{HY+foHY6@8`_iYtDaCKFO7Z4Nok3t-x*tGy%?zu)mce9%WCJGle&T z9R?ntpPi5YQ4V(j%SAC?j`*ImdwZ*#aPXRh>;lnerl`nKSzCZ=QJNlgJ}j4KOny>*q#t+wk@I5O3Nz%(r$ z&J)Z;ZclM-*`}W5Kn;B^udH~EDf3i&#r5jM3OufY%36Z*(vQH&hu}oolSMHn*62X! zmn(r2xHcUWt8Pu<1t`v{4%<9tSz@o2^)9G~0HV^C{-1qN#{k9wAR+Xq0G_B^n6IAw ztjgn}Y|9>{CxAMM&;X+EgMq%%UT__M*(O#!pk-iBv)@ytwd24Vv-Q>wRSz(1(6B$a z9Ml^=RlpX?#gmI(vndMh(_{g& za{Jo-zRsmuXJM(6Z9K>}9%dWsUv6u;8&kiG{7O37_ycYHp*Ft$`A(c+Egv=QBpcE; z<0BB@WA>MJWf1NGm)pKg_vf0O*Nb?Hx%!btlw19v-178%%dUC?qFlVpGM#`h6@W5C zi7hzZf#>}SOaMM$|LNcAVZ+06&df49y-JGTbw3YbYlZ!DEW{|aVLG`o>k-?Ht}5g| z%wXvDjobVzIoBpdd@`@Z|!$u!73{!^lya2zPIW_LG7xHO-y z#&Y`da5Io#We>~V;J(E>x2j(vX^LmfXc5{0BTXP2LkI$R(VY3jyS{X+r`(Wsje=!I>V==QKk5VWa~9TF?^;mR?@-_;SUvD3(ppD-gwCDDH#1p0Ey^ z9gTPrH7(sWjeJ=znQS*OSfFWN>(#X}wagip~mDqPrA*q}SNIn4Le5tMO^Q{4{4HHrj|S@_1KY zcBp__730XFT`Ga`&cWU`-M)-Egp=@h{&4K4_@l4R6CqRQ4ugLeUyj{i!_uqJ89d-B z`y{Q7;Un|-ieu8F{Yg$$+!dS7QhTXrnJuF(u!7v**)U0JH;KKLyT=Cc_#uS52>w$F z7K-+fm$`)!gdw&oFG&8=!EgpXv`0#HQkb?4(`r0t6}A5D{apti;orza$u=sMIZ5Y0 ohhLtPV&n1qglF&K56;O1>^TgZSGFd8BftNDQDq|?H+U@NAJk6niU0rr diff --git a/blog/__pycache__/views.cpython-311.pyc b/blog/__pycache__/views.cpython-311.pyc index 7511c9c2859b2a3295e148d749abd3d4648f4cf3..79071ce1c340f01570013ad29ab67c5d8b61a2ce 100644 GIT binary patch delta 284 zcmdlX@JWzwIWI340}zCH2dDY6P2`hcOq!@3%hkdV#h1br%%I7=v7>~oKA91u1`1e! zG&2x?Rss^!8A_1kY8X-&moYIgtOjBT02;yMS0$2^lb^0%kY8M)TauWRld6|dlAEK+ zSi}ZY1f&FjR58dHg(5*9d5a6AG9Ik5NE;}iVgOPg3?v#D?s5xGP`@B;c#+%a3b)Y( z7Na7NxSuBf H0<0GR*-bi6 delta 97 zcmew)xI=(%IWI340}xEVXrA_$bt0bxW6VVLSe9T0O|Ff7C2Y)ontYR&vS%xRZs$?1x!R*q`E~Llbs|ko7kO= z(+~+YNC^3a8qJ~f7C01D?F9vi3lfOGAV4Le6^C57Qbz;{QK{m+aXy_0G3)tx=J(#r zym|AR`MT@7=fYowLK=Z`H#<6UO=Bcg9!s6}=lztp$m_&ZJ|U);(d*jM z(}W(7ln*F%n+i(G50th=Ej{-71JDmbKa8Vv=@J4eyybE{A}JlH2n*eF6Y>CRz2u{2 zbd;oH;xz3~q^Z;KF5`A#wrH^n(}hX)T9H|%_&AtYxk*n`B?yBw@r|qNYzwq_D?skP zhexmzMTl+Fyct{4D}gQCy#&8#cE9SC25n``eTwFeqv-@dF70ca%8Rx*LKA?aa#k=) zGuDTV(_>4(6~bw$y1Z$UCf>Lm~>K1`MPd-%H>j#|JR9lV5~e5$H(T! zt%}KbCsGX=lvzCkh7432Pj_+WTZ=SRmGyYr?e3~?y(zUkxEjw?Rs7oOijlfX>-$db zgqnX*g=rjZit&;8JWyt>urD;*s7HC4a1O4=@7zix)G+c}vhSrd% zA>F?X9-qs<%oeD7;W=-d82_G_Qhk zz2ZseNalH%5tNHZ&0oN_%L&*wDh1Arab7ArAwR8x>~H z%1F}z$6W@0#jcbY=aY*qbBB~5Lj68Y4?ob7)m zfRMMJU%?itj-N-sHJy-Qj9UfU9%pb3jWp$xz}ghxB`zbojIId7XqXct(G**c_JROw z#517n0qsx`Mj}dUjr>2T>kk*p!5jV{`o^tebK4KNydv_|=(Tr}W6SM0`dcNF?a5jF zJ(SO$yg!4u2PsV{8M&oqmb~|`lvaxfriap>wrkc^QYP=Wa1!ver^NV%|lEwz<&YD)1TS^ delta 1416 zcma)5L1-IC6y343t6fPeX|~(!K6(cH*rPmf?G-zgAm?LaM)PR ztPN>SIfSA^pi%y%fwc9>rGy@W38hdBz4RE85U5cu?V)#vfwO zg$1P|rFE!?pu$K+kczf8`n4!7^cVV|?>L53+=;9OBIj5fnA}qo;T@jWeD>nEpP<L0pOY3$)r)o#17MGoK4*y`m zac%wvITS(VX+|gM4!Msw0t3F4T|eYLFqA#)K8z?owyvqYYdx4VeEm_4+8X9847t!L zHHMrO;3KdY+MAj0n9#Rl|1<{|^bGtRD!|veI?Dem2|@gCI|=+v*wC}5#*lmqUyuI0 z^77eCq?+=k(brTqQv<8x_sy}UN{?R+Q8P>5nIgEWRtH~~fg;SOn>S$LtOpueVZa6|5!6@8|?)Uv2xuM_6@e-2Lma;QE zvXu#v@RQyzpTy}Q_+1~cwoA^lq<3vHNgTmDFdUoA3u{`wQVePh8cz?wm$Bl(rgWCs zfvaj&Zc3L}Rl#xw+o_Kl-tq}&xxV1eI((t-E?jo_#}?zm>*aWz6{wfmA?{pUalB;_ zg%xQBmKsZK*;`q1IG=*Mi6NGU$BDh{I6P0}SOqdhSrIp524;+W;a&9MeS`4)inM7C z{ALb4G>2BtKQK#=%u-YR)64=GUwlG`*nLGrr&dtnGYDcr%PBG?7$>0umy!cVw}LS_yCwZU@LP#IOj`-~Ct3M4e!5{hJJRFdEfJXLqB3nM z$}Y+xB6zi4f2rda8up6o9OE@Krf|JE_|^On3aPo#C@XDAyW#qoN9nOABs|+z^Kd&= VJ!~*M&TjbX*b`!(y+#tJ`WLUn67m25 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/__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 0000000000000000000000000000000000000000..7b8466c31bd861b8d285a1881d4df7dc560275e0 GIT binary patch literal 1205 zcmZuv%}*0S6rcU*wz~x*3kiux0z?i?Ow&XqYK*B-F&IH2;Gt;Adb$4gk-70br z4jw#uVl!7y+{(v^lQX+rN`aZ3_pvkYH}C!C{pj!402@bh3(H3$ z0ROm?j8w-sE;3^aAb43LDCdO8XJo=L3p z8p&*?c9q_IcGJE7CFD#GvU(mdu^Fz4G?Yd1Zr?V`YUeDoi|D3U(ojFE{~A?ile%*v zl`9r8>HR{Xa0ePzfIS!(CM0e|OXp9O*fw?oSoN?GU<8c-8V-Us@i3KL;s;b)!5)D% z+c5DW&2}9>C(%^JGMsl*b{dvN)rM;k1Hpi>9nB^PThK9Vh7-xuat&LEfx4u&o-X9? zt+6$IFvRN3rMxUtG>!1G(XaxV!KfA(6$>*QV+~r^F@qJ4Ki*s}OEWOSg^+5HWr`XW zW_BNhs|~~AcX1D47dr?$)f)Ca-Y7Kl1fl6Dhr##Q^I3OTI4UbnOo^0F2j3=}GrZbaEO#Mi z7&4$e_~va`A)#K})7_@-w$rkz9Rem!xVR8IoWxG;>r=lL|2zmG3RnGb{Zpt< z?dcm$ed8iPe^ysEU-IdVCmH8b^u`q==VOk&5q6HMsLuJxqe}EXak*n0A%dA;)@kNZ z{JxL9N#+V{`DG^Bk{}3-0%0NqMiWz$4KXH~_MPK71s!uRYcs>O2_kX#W#OqurgVqI%aV+FI2omui)YTB-Whc{Q!PyfhK9 zCPd>qQbXM27%51sl4s31D@?o#y4sJ0LaEgAY4{wE2?uRWmhSe9C5fhs#tSUZ;v+m`#Xbc3*dXTeCc|TH#a1V9on2xbeQ^~lpq}C| zdci>tQOu_DD5mL6!x%$>oEKi|Pbofu`o0}?hJ2R`20D&}BL~`-mv(vN+tgg^Z>g1A z{2K$cvST?p^qJS_H`BL4=*9gFbaDjUba(`TRLq7O)+6-M#`TskiRX4C!WYs>s;JKdiyk;<%X)I!+g$xdMDtpcPrD=?X&8d69 zo1+{#W&UA~GA3ds=S1Gb`Tb=!Ye@-kHX5HsZ}#6aU+R;Uiptch(1~eu=PB#79)@#( y^P>EeF-&Al{wvaCmE96GCEA)q=c0&%MUjz-?j&*LVw0Y!_?BePlUQt6SnD@Ke;Nh= delta 549 zcmY+BOK1~u5XN_AH@nF`^51CN(3X-kYHLd%(Iy8Uc@!)nwMl~tVtTL3-V$n6=!GW0%hB?gl`*JU|+`M+b z(-q}7E^PI`O3eBPAi8+E%(PwBuEnicv%`@oN_yDn_Vzf8OuHdU*VtPYXMV$VQD;Se z;3(LO*8?Y^W2Og5Vkx$iXd{zkRxQzOKajY+ecwb`=KS&6+u)Z%5%H| z2_?lfsDBi`khh99TBu;qY)eBg$TQA9$?fq#ev)p~&<#EsOu=^ZNe$Cr+KvMg5eDei zF}O=-PCae^Z?{HM4RVgf%MW-#VWtDeIM))m+h1YzDMKFrr zp>7Q7m%yygVk@sQp4&Fk3*5wc@esY5!+j7VEMbUjDQIb0G?OLFqS>B;4gCAKfm2bs zSH(S4s^AfvjF7#Cep0I#6Q+0eOO&kQ0XmU}PZp7b{}Bl^iI7EPiq4ktCHWp=r`%_S zw!fvHEg*uw2 From d2ae1f749a60d6172c9acf964d93e3cce83fedb1 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 17:30:11 +0200 Subject: [PATCH 40/46] Finish JavaScript functions --- blog/static/blog/blog.js | 63 ++++++++++++++++++++++++++++++++++++++++ templates/base.html | 1 + 2 files changed, 64 insertions(+) diff --git a/blog/static/blog/blog.js b/blog/static/blog/blog.js index 1cbf243baa..e8447f2b68 100644 --- a/blog/static/blog/blog.js +++ b/blog/static/blog/blog.js @@ -1,3 +1,5 @@ + +/* const theNumber = 1 let yourName = 'Ben' @@ -7,3 +9,64 @@ if (theNumber === 1) { } 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) diff --git a/templates/base.html b/templates/base.html index d5a89fdb5a..0e8bd41381 100644 --- a/templates/base.html +++ b/templates/base.html @@ -5,6 +5,7 @@ {% block title %}Welcome to Blango{% endblock %} + From bbef1eac68733c076b179585c8949df93cafea2b Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 17:54:41 +0200 Subject: [PATCH 41/46] Finish JavaScript classes --- blog/static/blog/blog.js | 56 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/blog/static/blog/blog.js b/blog/static/blog/blog.js index e8447f2b68..d7c3a59d35 100644 --- a/blog/static/blog/blog.js +++ b/blog/static/blog/blog.js @@ -41,7 +41,7 @@ setTimeout(() => { console.log('After setTimeout') -*/ + for(let i = 0; i < 10; i += 1) { console.log('for loop i: ' + i) @@ -70,3 +70,57 @@ 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() From d064bfe877dc29c08783684277de04c75cda7aac Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 19:13:09 +0200 Subject: [PATCH 42/46] Update blog.js --- blog/static/blog/blog.js | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/blog/static/blog/blog.js b/blog/static/blog/blog.js index d7c3a59d35..7b79661bd1 100644 --- a/blog/static/blog/blog.js +++ b/blog/static/blog/blog.js @@ -1,4 +1,34 @@ +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' @@ -71,7 +101,7 @@ console.log('Here are the doubled numbers') console.log(doubled) -*/ + class Greeter { constructor (name) { @@ -124,3 +154,4 @@ dg2.greet() const dg1 = new DelayedGreeter('Patchu 1 Second', 1000) dg1.greet() +*/ \ No newline at end of file From d5d36adaa26c727b76ac3556bc95ffd4b340f9a6 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 19:34:42 +0200 Subject: [PATCH 43/46] Finish ReactJS --- blog/static/blog/blog.js | 51 +++++++++++++++++++++++++++++++++- templates/blog/post-table.html | 12 +++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/blog/static/blog/blog.js b/blog/static/blog/blog.js index 7b79661bd1..38e408ac01 100644 --- a/blog/static/blog/blog.js +++ b/blog/static/blog/blog.js @@ -1,4 +1,53 @@ + +//REACTJS CODE STARTS HERE +class ClickButton extends React.Component { + state = { + wasClicked: false + } + + handleClick () { + this.setState( + {wasClicked: true} + ) + } + + 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) } @@ -29,7 +78,7 @@ lazyAdd("nan", "alsonan").then(resolvedCallback, rejectedCallback) -/* + const theNumber = 1 let yourName = 'Ben' diff --git a/templates/blog/post-table.html b/templates/blog/post-table.html index 45c1d3f725..b7404c8eac 100644 --- a/templates/blog/post-table.html +++ b/templates/blog/post-table.html @@ -1,5 +1,15 @@ {% extends "base.html" %} {% load static %} {% block content %} - + +
+
+
+
+
+ + + + + {% endblock %} \ No newline at end of file From 1562a946878f4de0a86e875afa667a0aa8243c84 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 20:17:45 +0200 Subject: [PATCH 44/46] Finish JSX --- blog/static/blog/blog copy.js | 232 +++++++++++++++++++++++++++++ blog/static/blog/blog.js | 258 +++++++++------------------------ templates/blog/post-table.html | 10 +- 3 files changed, 310 insertions(+), 190 deletions(-) create mode 100644 blog/static/blog/blog copy.js 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 index 38e408ac01..1e0e0414bf 100644 --- a/blog/static/blog/blog.js +++ b/blog/static/blog/blog.js @@ -1,206 +1,86 @@ - - -//REACTJS CODE STARTS HERE -class ClickButton extends React.Component { - state = { - wasClicked: false - } - - handleClick () { - this.setState( - {wasClicked: true} - ) - } - +class PostRow extends React.Component { render () { - let buttonText - - if (this.state.wasClicked) - buttonText = 'Clicked!' - else - buttonText = 'Click Me' + const post = this.props.post - return React.createElement( - 'button', - { - className: 'btn btn-primary mt-2', - onClick: () => { - this.handleClick() - } - }, - buttonText - ) - } -} + let thumbnail -//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") + if (post.hero_image.thumbnail) { + thumbnail = } else { - const sum = a + b - resolve(sum) + thumbnail = '-' } - } - - 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) + return + {post.title} + + {thumbnail} + + {post.tags.join(', ')} + {post.slug} + {post.summary} + View + } } -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' +class PostTable extends React.Component { + state = { + dataLoaded: true, + data: { + results: [ + { + id: 15, + tags: [ + 'django', 'react' + ], + 'hero_image': { + 'thumbnail': '/media/__sized__/hero_images/snake-419043_1920-thumbnail-100x100-70.jpg', + 'full_size': '/media/hero_images/snake-419043_1920.jpg' + }, + title: 'Test Post', + slug: 'test-post', + summary: 'A test post, created for Django/React.' + } + ] } - - 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 + render () { + let rows + if (this.state.dataLoaded) { + if (this.state.data.results.length) { + rows = this.state.data.results.map(post => ) + } else { + rows = + No results found. + + } + } else { + rows = + Loading… + } - } - greet () { - setTimeout( - () => { - this.showGreeting(this.getGreeting()) - }, this.delay - ) + return + + + + + + + + + + + + {rows} + +
TitleImageTagsSlugSummaryLink
} } -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 +const domContainer = document.getElementById('react_root') +ReactDOM.render( + React.createElement(PostTable), + domContainer +) \ No newline at end of file diff --git a/templates/blog/post-table.html b/templates/blog/post-table.html index b7404c8eac..d576057aa7 100644 --- a/templates/blog/post-table.html +++ b/templates/blog/post-table.html @@ -1,5 +1,8 @@ {% extends "base.html" %} {% load static %} + +{% block title %}Post Table{% endblock %} + {% block content %}
@@ -10,6 +13,11 @@ - + + + + + + {% endblock %} \ No newline at end of file From c4d153c51d8af595ff500729565cc741d4d0b5a5 Mon Sep 17 00:00:00 2001 From: distance Date: Sun, 26 May 2024 21:34:52 +0200 Subject: [PATCH 45/46] Finish fetch and React hooks --- blog/static/blog/blog.js | 70 ++++++++++++++++++++++++++++++++-- blog/views.py | 6 ++- templates/blog/post-table.html | 3 ++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/blog/static/blog/blog.js b/blog/static/blog/blog.js index 1e0e0414bf..5e9b8a233b 100644 --- a/blog/static/blog/blog.js +++ b/blog/static/blog/blog.js @@ -1,3 +1,27 @@ +/*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 @@ -24,7 +48,41 @@ class PostRow extends React.Component { } class PostTable extends React.Component { + state = { + dataLoaded: false, + data: null + } + + componentDidMount () { + fetch(this.props.url).then(response => { + + if (response.status !== 200) { + throw new Error('Invalid status from server: ' + response.statusText) + } + + return response.json() + }).then(data => { + this.setState({ + dataLoaded: true, + data: data + }) + }).catch(e => { + console.error(e) + this.setState({ + dataLoaded: true, + data: { + results: [] + } + }) + }) + } + + + + + + /*state = { dataLoaded: true, data: { results: [ @@ -43,7 +101,7 @@ class PostTable extends React.Component { } ] } - } + }*/ render () { let rows @@ -77,10 +135,16 @@ class PostTable extends React.Component { } + } + + const domContainer = document.getElementById('react_root') ReactDOM.render( - React.createElement(PostTable), + React.createElement( + PostTable, + {url: postListUrl} + ), domContainer -) \ No newline at end of file +) diff --git a/blog/views.py b/blog/views.py index 647c2bd638..849117a80f 100644 --- a/blog/views.py +++ b/blog/views.py @@ -4,6 +4,8 @@ from django.shortcuts import redirect from blog.forms import CommentForm +from django.urls import reverse + # Create your views here. def index(request): posts = Post.objects.filter(published_at__lte=timezone.now()) @@ -46,4 +48,6 @@ def get_ip(request): return HttpResponse(request.META['REMOTE_ADDR']) def post_table(request): - return render(request, "blog/post-table.html") \ No newline at end of file + return render( + request, "blog/post-table.html", {"post_list_url": reverse("post-list")} + ) diff --git a/templates/blog/post-table.html b/templates/blog/post-table.html index d576057aa7..a3a5ca1224 100644 --- a/templates/blog/post-table.html +++ b/templates/blog/post-table.html @@ -15,6 +15,9 @@ + From af4f7dde2d6dec9a4d28ee6535c75a6eaa05886f Mon Sep 17 00:00:00 2001 From: distance Date: Mon, 27 May 2024 00:35:48 +0200 Subject: [PATCH 46/46] Create requests_test.py --- blango/requests_test.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 blango/requests_test.py 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