From 3e83e472b7ae8d2c587331d3b2c80ca4dc9f6eb5 Mon Sep 17 00:00:00 2001 From: tata Date: Sat, 4 Nov 2023 23:51:24 +0100 Subject: [PATCH 1/3] For #26 remove dj-extendion; make ordinary management command for sending news letters --- sandbox/conf/base.py | 23 +++-- sandbox/conf/dev.py | 2 +- src/contacts/admin.py | 1 + src/contacts/{jobs => management}/__init__.py | 0 src/contacts/management/commands/__init__.py | 0 .../commands/send_news_letter.py} | 15 +-- src/contacts/tests/test_jobs.py | 92 ++++++++++--------- src/contacts/tests/test_mail_fail.py | 21 ++--- src/posts/admin.py | 5 +- src/templates/contacts/emails/letter.html | 18 ++-- src/templates/posts/parts/single_post.html | 1 + 11 files changed, 91 insertions(+), 87 deletions(-) rename src/contacts/{jobs => management}/__init__.py (100%) create mode 100644 src/contacts/management/commands/__init__.py rename src/contacts/{jobs/send_news.py => management/commands/send_news_letter.py} (85%) diff --git a/sandbox/conf/base.py b/sandbox/conf/base.py index 8d76124..e1afc12 100644 --- a/sandbox/conf/base.py +++ b/sandbox/conf/base.py @@ -133,7 +133,7 @@ ] -LANGUAGE_CODE = "en-us" +LANGUAGE_CODE = "en" LANGUAGES = (("ru", _("Russian")), ("en", _("English")), ("uk", _("Ukrainian"))) USE_I18N = True @@ -167,8 +167,7 @@ EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") EMAIL_PORT = env("EMAIL_PORT") EMAIL_USE_TLS = True -EMAIL_BACKEND = env("EMAIL_BACKEND") -# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # dev.py +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # img upload limits @@ -392,14 +391,14 @@ # AWS -AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID") -AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY") -AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME") +# AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID") +# AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY") +# AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME") -AWS_S3_FILE_OVERWRITE = False -AWS_DEFAULT_ACL = None -DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" +# AWS_S3_FILE_OVERWRITE = False +# AWS_DEFAULT_ACL = None +# DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" -AWS_QUERYSTRING_AUTH = False # key will be not present in url -AWS_S3_MAX_MEMORY_SIZE = 2200000 -AWS_S3_REGION_NAME = "eu-central-1" +# AWS_QUERYSTRING_AUTH = False # key will be not present in url +# AWS_S3_MAX_MEMORY_SIZE = 2200000 +# AWS_S3_REGION_NAME = "eu-central-1" diff --git a/sandbox/conf/dev.py b/sandbox/conf/dev.py index 9bee611..4992326 100644 --- a/sandbox/conf/dev.py +++ b/sandbox/conf/dev.py @@ -13,10 +13,10 @@ "PORT": "5432", } } - # use in-memory-db # let op: email confirmation will be NOT in console # EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + USE_CAPCHA = False diff --git a/src/contacts/admin.py b/src/contacts/admin.py index 7a15c1c..ea429ae 100644 --- a/src/contacts/admin.py +++ b/src/contacts/admin.py @@ -10,6 +10,7 @@ class NewsLetterAdmin(admin.ModelAdmin): related posts status should be changd """ + list_display_links = ["title"] date_hierarchy = "added_at" search_fields = ("title", "letter_status") diff --git a/src/contacts/jobs/__init__.py b/src/contacts/management/__init__.py similarity index 100% rename from src/contacts/jobs/__init__.py rename to src/contacts/management/__init__.py diff --git a/src/contacts/management/commands/__init__.py b/src/contacts/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/contacts/jobs/send_news.py b/src/contacts/management/commands/send_news_letter.py similarity index 85% rename from src/contacts/jobs/send_news.py rename to src/contacts/management/commands/send_news_letter.py index b1a427b..2e2a6e9 100644 --- a/src/contacts/jobs/send_news.py +++ b/src/contacts/management/commands/send_news_letter.py @@ -2,9 +2,9 @@ from django.conf import settings from django.core import mail +from django.core.management.base import BaseCommand from django.template.loader import render_to_string from django.utils import timezone -from django_extensions.management.jobs import WeeklyJob from src.contacts.exceptions import * # noqa from src.contacts.models import NewsLetter @@ -12,18 +12,18 @@ from src.profiles.models import Profile -class Job(WeeklyJob): +class Command(BaseCommand): """ users: active status and profile wanted_niews - will get email with news weekly; + will get email with news weekly?; News letter may not contain links to posts TODO: better way to arrange unsubscribe """ - help = "Send news letter" # noqa + help = "Send a newsletter to subscribed users" # noqa - def execute(self): + def handle(self, *args, **options): _date = timezone.localdate() str_date = _date.strftime("%d/%m/%Y") stamp = f"Newsletter {_date:%A}, {_date:%b}. {_date:%d} {str_date}" @@ -31,9 +31,10 @@ def execute(self): profiles = Profile.objects.send_news().select_related("user") letter = NewsLetter.objects.filter(letter_status=1).last() ctx = {"letter": letter, "domain": domain} + if profiles and letter: try: - posts = Post.objects.filter(send_status=1, letter=letter) + posts = Post.objects.get_public().filter(send_status=1, letter=letter) ctx.update({"posts": posts}) for profile in profiles: ctx.update({"uuid": profile.uuid}) @@ -63,3 +64,5 @@ def execute(self): raise NewsFansNotFoundException("No profiles not send news") elif not letter: raise LetterNotFoundException("No letter to send") + + self.stdout.write(self.style.SUCCESS("Successfully sent newletter")) diff --git a/src/contacts/tests/test_jobs.py b/src/contacts/tests/test_jobs.py index 937e4b6..7d2b9a8 100644 --- a/src/contacts/tests/test_jobs.py +++ b/src/contacts/tests/test_jobs.py @@ -1,9 +1,11 @@ import time_machine from django.conf import settings +from django.core import mail +from django.core.management import call_command +from django.test import TestCase, override_settings from django.urls import reverse from src.contacts.exceptions import * # noqa -from src.contacts.jobs.send_news import Job as SendMailJob from src.contacts.models import NewsLetter from src.posts.models.post_model import Post from src.posts.tests.factories import PostFactory @@ -12,57 +14,60 @@ from .factories import NewsLetterFactory -class TestSendEmailJob: +@override_settings(LANGUAGE_CODE="ru", LANGUAGES=(("ru", "Russian"),)) +class TestSendEmailJob(TestCase): @time_machine.travel("2023-07-17 00:00 +0000") - def test_send_news_with_posts_links(self, mailoutbox): + def test_send_news_with_posts_links(self): """ active user (can) get news letter via email with corresp links to posts; if sending OK: letter and related posts - change their status + change their status; + email (html)text contains a link to unsubscribe """ subject = "Newsletter Monday, Jul. 17 17/07/2023" profile = ProfileFactory(want_news=True) domain = settings.ABSOLUTE_URL_BASE letter = NewsLetterFactory(letter_status=1) - post = PostFactory(send_status=1, letter=letter, title_ru="заголовок") + post = PostFactory(send_status=1, status=2, letter=letter, title_ru="заголовок") post_title = post.title_ru short_url = reverse("contacts:end_news", kwargs={"uuid": profile.uuid}) full_link_unsub = f"{domain}{short_url}" - send_mail_job = SendMailJob() - send_mail_job.execute() + call_command("send_news_letter") - assert len(mailoutbox) == 1 + self.assertEqual(len(mail.outbox), 1) - mail = mailoutbox[0] - html_msg = mail.alternatives[0][0] + sent_mail = mail.outbox[0] - assert mail.to == [profile.user.email] - assert mail.subject == subject + html_msg = sent_mail.alternatives[0][0] - assert letter.text in mail.body - assert letter.text in html_msg - # TODO: change title for a link to post - assert post_title in mail.body - assert post_title in html_msg - # email (html)text contains a link to unsubscribe - assert full_link_unsub in mail.body - assert full_link_unsub in html_msg + self.assertTrue(sent_mail.to == [profile.user.email]) + self.assertTrue(sent_mail.subject == subject) + self.assertTrue(letter.text in sent_mail.body) + self.assertTrue(letter.text in html_msg) + + self.assertIn(post_title, sent_mail.body) + self.assertIn(post_title, html_msg) + self.assertIn(full_link_unsub, sent_mail.body) + self.assertIn(full_link_unsub, html_msg) + + # after sending post_after = Post.objects.filter(send_status=2).last() letter_after = NewsLetter.objects.filter(letter_status=2).last() - assert post.id == post_after.id - assert post_after.send_status == 2 - assert letter.id == letter_after.id - assert letter_after.letter_status == 2 + self.assertEqual(post.id, post_after.id) + self.assertEqual(post_after.send_status, 2) + self.assertEqual(letter.id, letter_after.id) + self.assertEqual(letter_after.letter_status, 2) @time_machine.travel("2023-07-17 00:00 +0000") - def test_send_news_no_posts(self, mailoutbox): + def test_send_news_no_posts(self): """ No posts links in the letter no posts with send_status + email (html)text contains a link to unsubscribe """ subject = "Newsletter Monday, Jul. 17 17/07/2023" profile = ProfileFactory(want_news=True) @@ -73,29 +78,30 @@ def test_send_news_no_posts(self, mailoutbox): short_url = reverse("contacts:end_news", kwargs={"uuid": profile.uuid}) full_link_unsub = f"{domain}{short_url}" - send_mail_job = SendMailJob() - send_mail_job.execute() + call_command("send_news_letter") + + self.assertEqual(len(mail.outbox), 1) - assert len(mailoutbox) == 1 + sent_mail = mail.outbox[0] + html_msg = sent_mail.alternatives[0][0] + sent_mail = mail.outbox[0] + html_msg = sent_mail.alternatives[0][0] - mail = mailoutbox[0] - html_msg = mail.alternatives[0][0] + self.assertTrue(sent_mail.to == [profile.user.email]) + self.assertTrue(sent_mail.subject == subject) - assert mail.to == [profile.user.email] - assert mail.subject == subject + self.assertTrue(letter.text in sent_mail.body) + self.assertTrue(letter.text in html_msg) + self.assertNotIn(post_title, sent_mail.body) + self.assertNotIn(post_title, html_msg) - assert letter.text in mail.body - assert letter.text in html_msg - # TODO: change title for a link to post - assert post_title not in mail.body - assert post_title not in html_msg - # email (html)text contains a link to unsubscribe - assert full_link_unsub in mail.body - assert full_link_unsub in html_msg + self.assertIn(full_link_unsub, sent_mail.body) + self.assertIn(full_link_unsub, html_msg) + # after sending post_after = Post.objects.filter(send_status=2).count() letter_after = NewsLetter.objects.filter(letter_status=2).last() - assert post_after == 0 - assert letter.id == letter_after.id - assert letter_after.letter_status == 2 + self.assertEqual(post_after, 0) + self.assertEqual(letter.id, letter_after.id) + self.assertEqual(letter_after.letter_status, 2) diff --git a/src/contacts/tests/test_mail_fail.py b/src/contacts/tests/test_mail_fail.py index a8b953e..b771646 100644 --- a/src/contacts/tests/test_mail_fail.py +++ b/src/contacts/tests/test_mail_fail.py @@ -2,10 +2,10 @@ from unittest import mock from django.core import mail +from django.core.management import call_command from django.test import TestCase from src.accounts.models import User -from src.contacts.jobs.send_news import Job as SendMailJob from src.profiles.tests.factories.profile_factory import ProfileFactory from ..exceptions import * # noqa @@ -19,9 +19,7 @@ def test_news_not_send(self, mock_fail): letter = NewsLetterFactory(letter_status=1) # noqa mock_fail.side_effect = SMTPException - - send_mail_job = SendMailJob() - send_mail_job.execute() + call_command("send_news_letter") self.assertEqual(len(mail.outbox), 0) self.assertTrue(mock_fail.called) @@ -41,9 +39,10 @@ def test_manager_no_news_inactive_user(self): user.is_active = False user.save() letter = NewsLetterFactory(letter_status=1) # noqa - send_mail_job = SendMailJob() + with self.assertRaises(NewsFansNotFoundException) as e: - send_mail_job.execute() + call_command("send_news_letter") + self.assertEqual(str(e.exception), "No profiles not send news") self.assertEqual(len(mail.outbox), 0) @@ -54,9 +53,9 @@ def test_manager_no_news_fans(self): """ profile = ProfileFactory() # noqa letter = NewsLetterFactory(letter_status=1) # noqa - send_mail_job = SendMailJob() + with self.assertRaises(NewsFansNotFoundException) as e: - send_mail_job.execute() + call_command("send_news_letter") self.assertEqual(str(e.exception), "No profiles not send news") self.assertEqual(len(mail.outbox), 0) @@ -66,9 +65,9 @@ def test_no_letter_no_job(self): if no letter -> no SendMail """ profile = ProfileFactory(want_news=True) # noqa - send_mail_job = SendMailJob() + with self.assertRaises(LetterNotFoundException) as e: - send_mail_job.execute() + call_command("send_news_letter") self.assertEqual(str(e.exception), "No letter to send") - assert len(mail.outbox) == 0 + self.assertEqual(len(mail.outbox), 0) diff --git a/src/posts/admin.py b/src/posts/admin.py index 7f2b912..36274c4 100644 --- a/src/posts/admin.py +++ b/src/posts/admin.py @@ -36,7 +36,7 @@ class PostAdmin(TranslationAdmin): search_fields = ("title", "categ__name") list_display = [ "id", - # "author", + "send_status", "title", "status", "is_deleted", @@ -48,11 +48,10 @@ class PostAdmin(TranslationAdmin): "count_bmarks", ] list_select_related = ("categ", "author") - list_editable = ["is_deleted"] # , "status"] list_display_links = ["title"] radio_fields = {"status": admin.HORIZONTAL} save_on_top = True - list_filter = ["status", "created_at", SoftDelFilter] + list_filter = ["status", "created_at", SoftDelFilter, "send_status"] # list_filter = ["status", "created_at", "is_deleted"] list_per_page = 15 actions = ("make_posts_published", "set_to_draft", "set_to_review") diff --git a/src/templates/contacts/emails/letter.html b/src/templates/contacts/emails/letter.html index 15131eb..a7cc93e 100644 --- a/src/templates/contacts/emails/letter.html +++ b/src/templates/contacts/emails/letter.html @@ -5,27 +5,23 @@

Greetings from MedSandbox

-

Here is some fresh content on our site

{% if letter %} -

Letter title: {{letter.title}}

-

Letter text: {{ letter.text|linebreaksbr }}

-

Here is a link to read a new article: - {% if posts %} +

{{letter.title}}

+

{{ letter.text|linebreaksbr }}

+

Please follow the link to read more + {% if posts %} {% for post in posts %} - Post title: {{post.title_ru}} - {% comment %} - Some post

- {% endcomment %} + {{post.title}}

{% endfor%} {% else %} -

No posts

+

No links to posts

{% endif %} {% endif %}

Best regards,

Admin


- Unsubscribe + Unsubscribe from news letters
diff --git a/src/templates/posts/parts/single_post.html b/src/templates/posts/parts/single_post.html index c3bf7bb..8db839c 100644 --- a/src/templates/posts/parts/single_post.html +++ b/src/templates/posts/parts/single_post.html @@ -7,6 +7,7 @@

{{post.title}}

{% trans "category" %}: {{post.categ.name}}
{{post.content|safe}} +
More details From 337c116f4a27bf8de3783bc2854e7bf4d4a7e0d0 Mon Sep 17 00:00:00 2001 From: tata Date: Sun, 5 Nov 2023 00:03:32 +0100 Subject: [PATCH 2/3] add missed package boto3 --- reqs/reqlinux.txt | 17 +++++++++++++++++ src/profiles/models.py | 28 ++++++---------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/reqs/reqlinux.txt b/reqs/reqlinux.txt index 7f3ad5b..924e1f6 100644 --- a/reqs/reqlinux.txt +++ b/reqs/reqlinux.txt @@ -6,6 +6,12 @@ # asgiref==3.6.0 # via django +boto3==1.28.77 + # via -r reqs\reqlinux.in +botocore==1.31.78 + # via + # boto3 + # s3transfer certifi==2023.5.7 # via # requests @@ -59,6 +65,10 @@ django-widget-tweaks==1.4.12 # via -r reqs\reqlinux.in idna==3.4 # via requests +jmespath==1.0.1 + # via + # boto3 + # botocore oauthlib==3.2.2 # via requests-oauthlib pillow==9.5.0 @@ -73,6 +83,8 @@ pycparser==2.21 # via cffi pyjwt[crypto]==2.7.0 # via django-allauth +python-dateutil==2.8.2 + # via botocore python-magic==0.4.27 # via -r reqs\reqlinux.in python3-openid==3.2.0 @@ -86,8 +98,12 @@ requests==2.30.0 # requests-oauthlib requests-oauthlib==1.3.1 # via django-allauth +s3transfer==0.7.0 + # via boto3 sentry-sdk==1.2 # via -r reqs\reqlinux.in +six==1.16.0 + # via python-dateutil sqlparse==0.4.4 # via django typing-extensions==4.7.1 @@ -98,5 +114,6 @@ unidecode==1.3.6 # via -r reqs\reqlinux.in urllib3==2.0.2 # via + # botocore # requests # sentry-sdk diff --git a/src/profiles/models.py b/src/profiles/models.py index 856f285..9848954 100644 --- a/src/profiles/models.py +++ b/src/profiles/models.py @@ -1,7 +1,7 @@ import uuid -from boto3.session import Session -from django.conf import settings +# from boto3.session import Session +# from django.conf import settings from django.contrib.auth import get_user_model from django.db import models from django.shortcuts import reverse @@ -39,26 +39,10 @@ def get_absolute_url(self): def __str__(self) -> str: return self.user.username - # def delete(self): - # """if profile obj deleted; remove avatar file""" - # self.avatar.delete() - # super().delete() - # def delete(self, *args, **kwargs): - # session = Session (settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY) - # s3_resource = session.resource('s3') - # s3_bucket = s3_resource.Bucket(settings.AWS_STORAGE_BUCKET_NAME) - - # file_path = f"avatar/{self.id}" + str(self.avatar) - # print("path is ",file_path) - # response = s3_bucket.delete_objects( - # Delete={ - # 'Objects': [ - # { - # 'Key': file_path - # } - # ] - # }) - # super().delete(*args, **kwargs) + def delete(self): + """if profile obj deleted; remove avatar file""" + self.avatar.delete() + super().delete() class ProfileChart(Profile): From db1ce149d4514ed4d94ee0801eb4af71be86e45f Mon Sep 17 00:00:00 2001 From: tata Date: Sun, 5 Nov 2023 22:36:34 +0100 Subject: [PATCH 3/3] For #74 :lipstick: change flex for grid remove prints ,console.log --- src/comments/tests/test_form.py | 1 - src/core/utils/tree_help.py | 2 - src/devs/views.py | 2 - src/notifications/tests/test_views.py | 2 +- src/posts/templatetags/calend.py | 2 - src/posts/views/post_views.py | 3 +- src/profiles/forms.py | 1 - src/static/css/search.css | 1 + src/static/css/styles.css | 149 ++++++++++-------- src/static/css/temp_test.css | 20 --- src/static/js/bookmark.js | 3 - src/static/js/checkState.js | 3 - src/static/js/utils.js | 20 ++- src/templates/base.html | 2 +- src/templates/components/categ_bar.html | 5 +- src/templates/components/tags.html | 14 +- src/templates/posts/parts/archive.html | 6 +- src/templates/posts/parts/post_card.html | 2 - .../posts/parts/posts_collection.html | 1 - src/templates/posts/parts/single_post.html | 6 +- src/templates/posts/post_detail.html | 7 +- src/templates/posts/post_list.html | 17 +- 22 files changed, 129 insertions(+), 140 deletions(-) delete mode 100644 src/static/css/temp_test.css diff --git a/src/comments/tests/test_form.py b/src/comments/tests/test_form.py index 8a4fb07..e87abb9 100644 --- a/src/comments/tests/test_form.py +++ b/src/comments/tests/test_form.py @@ -97,7 +97,6 @@ def test_error_submit_no_body_root_commet(self): div_comms = resp_submit.html.find("div", class_="comms") msg_txt = _("This field is required.") - print(resp_submit.context["form"].errors) self.assertEqual(self.resp.status_code, 200) self.assertEqual(resp_submit.status_code, 200) diff --git a/src/core/utils/tree_help.py b/src/core/utils/tree_help.py index 183ebfc..1df7d5c 100644 --- a/src/core/utils/tree_help.py +++ b/src/core/utils/tree_help.py @@ -37,8 +37,6 @@ def get_cached_trees(queryset) -> list: for obj in queryset: obj._cached_children = [] if obj.depth == queryset[0].depth: - # print("line 45 obj depth == qs[0]") - # print("adding to top node") add_top_node(obj, top_nodes, path) else: while not is_child_of(obj, parent := path[-1]): diff --git a/src/devs/views.py b/src/devs/views.py index 3c9d2c0..a783c43 100644 --- a/src/devs/views.py +++ b/src/devs/views.py @@ -57,7 +57,6 @@ def get_queryset(self): def get_context_data(self, **kwargs): """author can see in admin their permitted objects""" ctx = super().get_context_data(**kwargs) - print("header is ", self.header) ctx["header"] = self.header # if self.request.user.has_perm("posts.add_post"): @@ -112,7 +111,6 @@ class ChangeState(SURM, View): def post(self, request, **kwargs): uuid = request.POST.get("uuid", None) current_state = request.POST.get("current_state") - print("current status", current_state) post = get_object_or_404( Post, uuid=uuid, status=current_state, author=self.request.user ) diff --git a/src/notifications/tests/test_views.py b/src/notifications/tests/test_views.py index cd72543..203970a 100644 --- a/src/notifications/tests/test_views.py +++ b/src/notifications/tests/test_views.py @@ -115,7 +115,7 @@ def test_failure_notific_as_read(self): url = reverse( "notifications:mark_top_read", ) - print("I am here") + with self.assertRaises(HtmxFailureError) as e: resp = self.client.post(url) # noqa diff --git a/src/posts/templatetags/calend.py b/src/posts/templatetags/calend.py index 9da2102..2be3aab 100644 --- a/src/posts/templatetags/calend.py +++ b/src/posts/templatetags/calend.py @@ -12,12 +12,10 @@ def show_archive(**kwargs): """substitute template sidebar `calender` with dropdown archive""" arch = Post.objects.get_public().datetimes("published_at", "month", order="DESC") - # print("arch is", arch.count()) # amount months with posts archives = {} for item in arch: year = item.year month = item.month - print("month ", month) for i in range(1, 13): if i == month: try: diff --git a/src/posts/views/post_views.py b/src/posts/views/post_views.py index c1f627f..014f6f7 100644 --- a/src/posts/views/post_views.py +++ b/src/posts/views/post_views.py @@ -22,7 +22,7 @@ class PostList(PostListMenuMixin, ListView): template_name = "posts/post_list.html" context_object_name = "posts" - paginate_by = 4 + paginate_by = 6 def get_queryset(self): return ( @@ -93,7 +93,6 @@ def post(self, request, *args, **kwargs): Comment.add_root(instance=comm) return self.form_valid(form) else: - print("form invalid") return self.form_invalid(form) def get_success_url(self): diff --git a/src/profiles/forms.py b/src/profiles/forms.py index 6ad9ee7..1888d39 100644 --- a/src/profiles/forms.py +++ b/src/profiles/forms.py @@ -53,7 +53,6 @@ def clean(self, *args, **kwargs): params={"size": filesizeformat(min_upload_size)}, ) if up_file.size > max_upload_size: - # print("too big ...") raise ValidationError( err_msg["max_size"], params={"size": filesizeformat(max_upload_size)}, diff --git a/src/static/css/search.css b/src/static/css/search.css index 84774c0..b22e140 100644 --- a/src/static/css/search.css +++ b/src/static/css/search.css @@ -129,6 +129,7 @@ } .not-found{ background-color: #b1c3b28a; + border-radius: 10px; } /* end categs search */ diff --git a/src/static/css/styles.css b/src/static/css/styles.css index 9d51826..a7ee760 100644 --- a/src/static/css/styles.css +++ b/src/static/css/styles.css @@ -16,7 +16,7 @@ body padding-top: 165px; html,body{ margin:0; padding: 0; - background-color:#f1dfc5; + background-color:#f0e1cd; font-family:"Poppins",sans-serif; height: 100%; } @@ -25,6 +25,28 @@ body{ /* width added new 27 april 2023 */ width: 100%; } +/* ######################### +scroll to top button +######################### */ +#toTop { + /* display: none; */ /*default */ + position: fixed; + bottom: 20px; + right: 30px; + z-index: 99; + border: none; + outline: none; + background-color:rgb(165, 46, 6); + color: white; + cursor: pointer; + padding: 15px; + border-radius: 10px; + font-size: 18px; +} + +#toTop:hover { + background-color: #555; +} @media screen and (min-width: 600px){ body{ padding-top:150px; @@ -32,6 +54,8 @@ body{ } .wraps { min-height: 100%; + /* max-width:100%; */ + overflow: hidden; display:flex; flex-direction: column; } @@ -45,7 +69,7 @@ footer{ .red{ color: red; } - +/* for likes .redish */ .redish{ color: rgb(234, 164, 164); } @@ -55,24 +79,14 @@ footer{ padding:0.5rem; border-radius: 5px; } -.note{ - background-color: #7db099; - color:white; - padding:5px 10px; - border-radius: 5px; -} -.or{ - background-color: #f0551d; -} +/* .green in flash msg */ .green{ background-color:#1987547d; padding:10px; border-radius: 10px; } -.blue{ - background-color: #296684; -} +/* for admin? */ .wide{ width: 100%; } @@ -83,16 +97,63 @@ footer{ margin:20px 0; border-radius: 5px; } - /* override bootstrap 5 */ .breadcrumb-item { color:#7e736f; - font-size: 1.5rem; } +/* breadcrum a tag */ .breadcrumb-item a{ - color:#0f3649 + color:#0f3649; + text-decoration: none; } +/* test with grid layout */ +.wrap{ + display:grid; + grid-gap:1.5rem; + grid-template-columns:1fr; + /* grid-template-rows: 300px 600px 300px 300px; */ +} +.grid-tags{ + grid-row:4/5; +} +.grid-categs{ + grid-row:1/2; +} +.grid-content{ + grid-row:3/4; + padding-left:1rem; +} +.grid-archive{ + grid-row:2/3; + } + +@media (min-width:700px){ +.wrap{ + display:grid; + grid-gap:1rem; + grid-template-columns: 18% 60% 22% ; + /* padding-block:2rem; */ + width:100%; + /* margin-inline: auto; */ + } +.grid-categs{ + grid-row:1/3; + } +.grid-content{ + grid-row:1/3; + padding-left:1rem; + } +.grid-archive{ + grid-row:1/2; + } +.grid-tags{ + grid-row:2/3; + } +} +/* end test with grid layout */ + + /* override bootstrap 5 */ .btn-secondary { background-color: rgb(165, 46, 6); @@ -134,7 +195,7 @@ footer{ background-color:#302725; border-color:#302725; color:white; - /* color:rgb(212, 205, 177); */ + } .btn-warning { background-color:#c34b14cf; @@ -434,58 +495,6 @@ a.reply:visited { --color-10: "#36654f", } -.color-8{ - fill:coral; -} -.color-2{ - fill: darkmagenta; -} -.color-3{ - fill: firebrick; -} -.color-4{ - fill: sienna; -} -.color-5{ - fill: silver; -} -.color-6{ - fill: forestgreen; -} -.color-7{ - fill: teal; - -} -.color-1{ - fill: rgb(220, 81, 56); -} -.color-9{ - fill: goldenrod; -} -.color-10{ - fill: orangered; -} -.avat { - width: 48px; - height: 48px; - border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; -} -.letter{ - fill:white; - -} - -/* search result highlight */ -/* todo ... */ -.search-result{ - color: #f0551d; - font-weight: 900; -} - - /* ******************* Pagination ******************* */ diff --git a/src/static/css/temp_test.css b/src/static/css/temp_test.css deleted file mode 100644 index dd5a579..0000000 --- a/src/static/css/temp_test.css +++ /dev/null @@ -1,20 +0,0 @@ -.first{ - font-size: 1.2em; - background-color: brown; - color:white; - height: 250px; - width: 250px; -} -.second{ - font-size: 1em; - background-color: rgb(239, 127, 15); - color:white; - height: 150px; - width: 150px; -} -.third{ - font-size:0.8em; - background-color: rgb(230, 222, 222); - height: 100px; - width: 100px; -} \ No newline at end of file diff --git a/src/static/js/bookmark.js b/src/static/js/bookmark.js index 018c6b2..167a494 100644 --- a/src/static/js/bookmark.js +++ b/src/static/js/bookmark.js @@ -20,13 +20,10 @@ .then((data)=>{ if(data.status_code ===200){ let msg = data.msg; - console.log("msg from the server ",msg) jsBox.classList.add("green","slide"); jsBox.textContent= msg; if(data.del_button){ - console.log("removing bmark button from the DOM"); bmarkDiv.remove(); - console.log("div removed"); } } else if(data.status_code ===404){ diff --git a/src/static/js/checkState.js b/src/static/js/checkState.js index bf51826..231293f 100644 --- a/src/static/js/checkState.js +++ b/src/static/js/checkState.js @@ -6,7 +6,6 @@ const checkInp = (checkbox,inp)=>{ 2.toggle class 'disabled' on submit button */ if(checkbox){ - console.log("checkbox in DOM; line 9") // checkbox(remove avatar) present in DOM: // checked vs unchecked checkbox.addEventListener("change",(e)=>{ @@ -37,7 +36,6 @@ const checkInp = (checkbox,inp)=>{ // or "attach img" but not both if(checkbox!=null){ // remove existed avatar (clear image input if user attached img occasionally); - console.log("checkbox NOT null; line 40") checkbox.addEventListener("change",(e)=>{ if(checkbox.checked){ inp.value = null @@ -55,7 +53,6 @@ const checkInp = (checkbox,inp)=>{ } }) }else{ - console.log("initial default state;line 58") // checkbox NOT in DOM (avatar == default static image) inp.addEventListener("change",()=>{ // toggle class 'disabled' on submit button diff --git a/src/static/js/utils.js b/src/static/js/utils.js index 32db025..976d666 100644 --- a/src/static/js/utils.js +++ b/src/static/js/utils.js @@ -63,8 +63,6 @@ let helpUtil = (url,name)=>{ u8arr[lenData]= bstr.charCodeAt(lenData); } const file = new File([u8arr],name,{type:mime}) - // console.log("final file ext is ",mime) - // console.log("w,h do not exist") return file; } @@ -86,7 +84,25 @@ function fileToDataUri(file) { }); } +// button to the topFunction//Get the button +var toTop = document.getElementById("toTop"); +// When the user scrolls down 20px from the top of the document, show the button +window.onscroll = function() {scrollFunction()}; + +function scrollFunction() { + if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 60) { + toTop.style.display = "block"; + } else { + toTop.style.display = "none"; + } +} + +// When the user clicks on the button, scroll to the top of the document +function topFunction() { + document.body.scrollTop = 0; + document.documentElement.scrollTop = 0; +} diff --git a/src/templates/base.html b/src/templates/base.html index 24ec18a..7b88d69 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -24,7 +24,6 @@ - @@ -88,6 +87,7 @@
+