From 2445fadf6395daa791f45a97882a0a6c33026e5c Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Tue, 9 Jan 2024 18:11:22 +1100 Subject: [PATCH] various interface changes --- README.md | 11 ++- ausglamr/settings.py | 10 +-- ausglamr/urls.py | 6 +- blogs/admin.py | 9 ++- .../management/commands/send_weekly_email.py | 4 +- blogs/models/blog.py | 10 ++- blogs/models/event.py | 8 +- blogs/models/group.py | 4 +- blogs/models/newsletter.py | 6 +- blogs/templates/browse/blogs.html | 34 +++++--- blogs/templates/browse/cfp.html | 6 +- blogs/templates/browse/events.html | 51 ++++++------ blogs/templates/browse/groups.html | 20 +++-- blogs/templates/browse/newsletters.html | 7 +- blogs/templates/events/cfp.html | 31 +------- blogs/templates/layout.html | 1 + blogs/templates/thanks.html | 2 + blogs/tests/test_commands.py | 1 - blogs/views/public.py | 79 +++++++++++-------- docker-compose.yml | 4 +- glamr-dev | 5 ++ 21 files changed, 172 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index 2577cc0..d508298 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,18 @@ A django app running on Docker. Replaces _Aus GLAM Blogs_. ## Deploy * `docker compose build` -* `./glamr-dev createsuperuser` -* `./glamr-dev makemigrations` +* `./glamr-dev makemigrations` (may get a DB connection error here, ignore it or run again) * `./glamr-dev migrate` +* `./glamr-dev createsuperuser` * `docker compose up` * set up database backups (as cron jobs): - * `/usr/bin/docker exec -u root ausglamr-db-1 pg_dump -v -Fc -U ausglamr -d "ausglamr" -f /tmp/ausglamr_backup.dump` - * `/usr/bin/docker cp ausglamr-db-1:/tmp/ausglamr_backup.dump /home/hugh/backups/` +* `./glamr-dev backup` + + * `/snap/bin/docker exec -u root ausglamr_db_1 pg_dump -v -Fc -U ausglamr -d "ausglamr" -f /tmp/ausglamr_backup.dump` + * `/snap/bin/docker cp ausglamr_db_1:/tmp/ausglamr_backup.dump /home/hugh/backups/` + * `mv /home/hugh/backups/ausglamr_backup.dump /home/hugh/backups/ausglamr_backup_$(date +'%a').dump` * set up cron jobs for management commands as below diff --git a/ausglamr/settings.py b/ausglamr/settings.py index 9027ea8..8df39b4 100644 --- a/ausglamr/settings.py +++ b/ausglamr/settings.py @@ -32,11 +32,11 @@ SECRET_KEY = env("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env("DEBUG") -ALLOWED_HOSTS = [env("DOMAIN")] -CSRF_COOKIE_SECURE=True -SESSION_COOKIE_SECURE=True +ALLOWED_HOSTS = ['.localhost', '127.0.0.1', '[::1]', env("DOMAIN")] +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = True CSRF_TRUSTED_ORIGINS = [f'https://{env("DOMAIN")}'] -CONN_MAX_AGE=None # persistent DB connection +CONN_MAX_AGE = None # persistent DB connection # Application definition @@ -180,4 +180,4 @@ LOGGING = { "handlers": ["console"], "level": "INFO", }, -} \ No newline at end of file +} diff --git a/ausglamr/urls.py b/ausglamr/urls.py index 3f392a2..31123cb 100644 --- a/ausglamr/urls.py +++ b/ausglamr/urls.py @@ -15,10 +15,14 @@ urlpatterns = [ path("contribute", views.Contribute.as_view(), name="contribute"), path("contact", views.Contact.as_view(), name="contact"), path("blogs", views.Blogs.as_view(), name="blogs"), + path("blogs/", views.Blogs.as_view(), name="blog-category "), path("events", views.Conferences.as_view(), name="events"), - path("cfps", views.CallsForPapers.as_view(), name="cfps"), + path("events/", views.Conferences.as_view(), name="events-category"), + re_path(r"^cfps/?$", views.CallsForPapers.as_view(), name="cfps"), path("groups", views.Groups.as_view(), name="groups"), + path("groups/", views.Groups.as_view(), name="group-category"), path("newsletters", views.Newsletters.as_view(), name="newsletters"), + path("newsletters/", views.Newsletters.as_view(), name="newsletters-category"), path("register-blog", views.RegisterBlog.as_view(), name="register-blog"), path( "submit-blog-registration", diff --git a/blogs/admin.py b/blogs/admin.py index acc0fba..1e0e5ba 100644 --- a/blogs/admin.py +++ b/blogs/admin.py @@ -20,9 +20,7 @@ def approve(modeladmin, request, queryset): if hasattr(instance, "event"): # CFP recipient = instance.event.contact_email - if hasattr( - instance, "contact_email" - ): # overrides above in case needed in future + if hasattr(instance, "contact_email"): recipient = instance.contact_email if recipient: @@ -32,7 +30,10 @@ def approve(modeladmin, request, queryset): title = instance.title subject = f"✅ {title} has been approved on AusGLAMR!" - message = f"

{title} has been approved on AusGLAMR. Hooray!

" + message = f"

{title} has been approved on AusGLAMR. Hooray!

" + if type(instance).__name__ == "Event": + message += f"

You can now optionally register a call for papers.

" + message += "" utilities.send_email(subject, message, recipient) diff --git a/blogs/management/commands/send_weekly_email.py b/blogs/management/commands/send_weekly_email.py index 4f54ee9..d96df78 100644 --- a/blogs/management/commands/send_weekly_email.py +++ b/blogs/management/commands/send_weekly_email.py @@ -28,7 +28,9 @@ class Command(BaseCommand): ) cutoff = timezone.now() - timedelta(days=7) - blogs = models.Blog.objects.filter(approved=True, updateddate__gte=cutoff) + blogs = models.Blog.objects.filter( + approved=True, active=True, added__gte=cutoff + ) articles = models.Article.objects.filter(pubdate__gte=cutoff) events = models.Event.objects.filter(approved=True, pub_date__gte=cutoff) cfps = models.CallForPapers.objects.filter( diff --git a/blogs/models/blog.py b/blogs/models/blog.py index ea282e6..ab3e139 100644 --- a/blogs/models/blog.py +++ b/blogs/models/blog.py @@ -25,7 +25,7 @@ class BlogData(models.Model): """Base bloggy data""" title = models.CharField(max_length=2000) - author_name = models.CharField(max_length=1000, null=True) + author_name = models.CharField(max_length=1000, null=True, blank=True) url = models.URLField(max_length=2000, unique=True) description = models.TextField(null=True, blank=True) updateddate = models.DateTimeField() @@ -55,6 +55,7 @@ class Blog(BlogData): feed = models.URLField(max_length=2000) category = models.CharField(choices=Category.choices, max_length=4) + added = models.DateTimeField() approved = models.BooleanField(default=False) announced = models.BooleanField(default=False) failing = models.BooleanField(default=False, blank=True, null=True) @@ -66,6 +67,11 @@ class Blog(BlogData): ) contact_email = models.EmailField(blank=True, null=True) + def save(self, *args, **kwargs): + if not self.added: + self.added = timezone.now() + super().save(*args, **kwargs) + def announce(self): """queue announcement""" @@ -108,7 +114,7 @@ class Article(BlogData): blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="articles") pubdate = models.DateTimeField() guid = models.CharField(max_length=2000) - tags = models.ManyToManyField("Tag", related_name="articles") + tags = models.ManyToManyField("Tag", related_name="articles", null=True, blank=True) # pylint: disable=undefined-variable def announce(self): diff --git a/blogs/models/event.py b/blogs/models/event.py index b7a4951..411957a 100644 --- a/blogs/models/event.py +++ b/blogs/models/event.py @@ -9,10 +9,10 @@ from .utils import Announcement, Category class Event(models.Model): """a event""" - name = models.CharField(max_length=999) + name = models.CharField(max_length=100) category = models.CharField(choices=Category.choices, max_length=4) url = models.URLField(max_length=400, unique=True) - description = models.TextField(null=True, blank=True) + description = models.TextField(null=True, blank=True, max_length=250) pub_date = models.DateTimeField() # for RSS feed start_date = models.DateField() announcements = models.IntegerField(null=True, blank=True, default=0) @@ -55,9 +55,9 @@ class CallForPapers(models.Model): """a event call for papers/presentations""" name = models.CharField( - max_length=999 + max_length=100 ) # "Call for papers", "call for participation" etc - details = models.TextField(null=True, blank=True) + details = models.TextField(null=True, blank=True, max_length=250) pub_date = models.DateTimeField(null=True, default=None) opening_date = models.DateField() closing_date = models.DateField() diff --git a/blogs/models/group.py b/blogs/models/group.py index 50f713a..1056de1 100644 --- a/blogs/models/group.py +++ b/blogs/models/group.py @@ -9,12 +9,12 @@ from .utils import Announcement, Category, GroupType class Group(models.Model): """a group on email, discord, slack etc""" - name = models.CharField(max_length=999) + name = models.CharField(max_length=100) category = models.CharField(choices=Category.choices, max_length=4) type = models.CharField(choices=GroupType.choices, max_length=4) url = models.URLField(max_length=400, unique=True) registration_url = models.URLField(max_length=400, unique=True) - description = models.TextField(null=True, blank=True) + description = models.TextField(null=True, blank=True, max_length=250) contact_email = models.EmailField(blank=True, null=True) announced = models.BooleanField(default=False) approved = models.BooleanField(default=False) diff --git a/blogs/models/newsletter.py b/blogs/models/newsletter.py index 3346a91..2e20b8c 100644 --- a/blogs/models/newsletter.py +++ b/blogs/models/newsletter.py @@ -9,12 +9,12 @@ from .utils import Announcement, Category class Newsletter(models.Model): """a newsletter""" - name = models.CharField(max_length=999) - author = models.CharField(max_length=999) + name = models.CharField(max_length=100) + author = models.CharField(max_length=100) category = models.CharField(choices=Category.choices, max_length=4) url = models.URLField(max_length=400, unique=True) - description = models.TextField(null=True, blank=True) + description = models.TextField(null=True, blank=True, max_length=250) activitypub_account_name = models.CharField(max_length=200, blank=True, null=True) contact_email = models.EmailField(blank=True, null=True) announced = models.BooleanField(default=False) diff --git a/blogs/templates/browse/blogs.html b/blogs/templates/browse/blogs.html index 0c7fd96..5b1fca7 100644 --- a/blogs/templates/browse/blogs.html +++ b/blogs/templates/browse/blogs.html @@ -1,20 +1,28 @@ {% extends "layout.html" %} {% block content %} -
-
Title
-
Category
-
Last updated
+ +
+
Title
+
Category
+
Last updated
+
+
+{% for blog in blogs %} +
+
+

{{blog.title}}

+

{{blog.description}}

-
- {% for blog in blogs %} -
-
{{blog.title}}

{{blog.description}}

-
{{blog.category_name}}
-
{{ blog.updateddate|date:"D d M Y" }} -
+ +
{{ blog.updateddate|date:"D d M Y" }}
-
- {% endfor %} +
+
+{% empty %} +

Oh no! There are no blogs currently registered. Try checking out some newsletters.

+{% endfor %} {% endblock %} \ No newline at end of file diff --git a/blogs/templates/browse/cfp.html b/blogs/templates/browse/cfp.html index 47b4f72..d73049b 100644 --- a/blogs/templates/browse/cfp.html +++ b/blogs/templates/browse/cfp.html @@ -10,7 +10,7 @@
{% for cfp in cfps %} -
+

{{cfp.name}}

{{cfp.details}}

@@ -19,7 +19,9 @@ {{cfp.closing_date}}

- {% endfor %} + {% empty %} +

Oh no! There are no Calls for Papers currently open. Try checking out some newsletters.

+ {% endfor %} {% endblock %} \ No newline at end of file diff --git a/blogs/templates/browse/events.html b/blogs/templates/browse/events.html index d596201..c0a67a3 100644 --- a/blogs/templates/browse/events.html +++ b/blogs/templates/browse/events.html @@ -2,30 +2,33 @@ {% block content %} -
- Name - Category - Start Date - Description -
-
- - - {% for con in cons %} -
{{con.call_for_papers.closing_date}}
-
- {{con.name}} - {{con.category_name}} - {{con.start_date}} -
-
{{con.description}}
- {% if con.call_for_papers %} - - {% endif %} -
+ +
+ Description + Category + Start Date +
+
+{% for con in cons %} +
+
+

{{con.name}}

+
+

{{con.description}}

+ {% if con.call_for_papers %} +

{{con.call_for_papers.name}} closes {{ con.call_for_papers.closing_date|date:"D d M" }}

+ {% endif %}
-
- {% endfor %} - +
+ {{con.category_name}} + {{con.start_date}} +
+
+{% empty %} +

Oh no! There are no events currently registered. Try checking out some blogs.

+{% endfor %} {% endblock %} \ No newline at end of file diff --git a/blogs/templates/browse/groups.html b/blogs/templates/browse/groups.html index 58de2a3..3036bb7 100644 --- a/blogs/templates/browse/groups.html +++ b/blogs/templates/browse/groups.html @@ -1,20 +1,26 @@ {% extends "layout.html" %} {% block content %} -
- Name - Category + +
Description + Category Registration Link

{% for group in groups %} -
- {{group.name}} - {{group.category_name}} - {{group.description}} +
+ {% empty %} +

Oh no! There are no groups currently registered. Try checking out some newsletters.

{% endfor %} {% endblock %} \ No newline at end of file diff --git a/blogs/templates/browse/newsletters.html b/blogs/templates/browse/newsletters.html index 3008ebf..ec39592 100644 --- a/blogs/templates/browse/newsletters.html +++ b/blogs/templates/browse/newsletters.html @@ -1,6 +1,9 @@ {% extends "layout.html" %} {% block content %} +
Title Author @@ -11,8 +14,10 @@
{{pub.name}} {{pub.author}} - {{pub.category_name}} + {{pub.category_name}}

+ {% empty %} +

Oh no! There are no newsletter currently registered. Try checking out some blogs.

{% endfor %} {% endblock %} \ No newline at end of file diff --git a/blogs/templates/events/cfp.html b/blogs/templates/events/cfp.html index 2800176..50744e8 100644 --- a/blogs/templates/events/cfp.html +++ b/blogs/templates/events/cfp.html @@ -27,36 +27,7 @@ {% csrf_token %}
- {% if conf_name %} - - {% else %} - - {% if errors %} {{ form.event.errors }} {% endif %} - {{ form.event }} - {% endif %} - - - {% if errors %} {{ form.name.errors }} {% endif %} -

e.g. "Call for Papers", "Request for submissions" etc

- - {% if errors %} {{ form.details.errors }} {% endif %} - - - -
- - {% if errors %} {{ form.opening_date.errors }} {% endif %} - - {% if errors %} {{ form.opening_date.errors }} {% endif %} - {{ form.opening_date }} - - - - {% if errors %} {{ form.closing_date.errors }} {% endif %} - {{ form.closing_date }} - -
- + {{ form }}
diff --git a/blogs/templates/layout.html b/blogs/templates/layout.html index 1a8df98..6d6a0d4 100644 --- a/blogs/templates/layout.html +++ b/blogs/templates/layout.html @@ -7,6 +7,7 @@ {{ title }} + diff --git a/blogs/templates/thanks.html b/blogs/templates/thanks.html index 9c8776e..3ff889a 100644 --- a/blogs/templates/thanks.html +++ b/blogs/templates/thanks.html @@ -9,6 +9,8 @@ {% if register_type == "email address" %}

Check your inbox to confirm your subscription. You can opt-out at any time by clicking the link at the bottom of the weekly emails.

+ {% elif register_type == "conference" %} +

Once your conference is approved, you will be able to register a Call for Papers if you would like to do so.

{% else %}

Your {{ register_type }} registration will be reviewed before being added to Aus GLAMR.

{% endif %} diff --git a/blogs/tests/test_commands.py b/blogs/tests/test_commands.py index eb55417..a2013df 100644 --- a/blogs/tests/test_commands.py +++ b/blogs/tests/test_commands.py @@ -205,7 +205,6 @@ class CommandsTestCase(TestCase): # should not be announced self.assertEqual(models.Announcement.objects.count(), 0) - def test_check_feeds_exclude_tag(self): """test parse a feed with exclude tag""" diff --git a/blogs/views/public.py b/blogs/views/public.py index c1b5e1b..ebe03b5 100644 --- a/blogs/views/public.py +++ b/blogs/views/public.py @@ -6,6 +6,7 @@ from operator import attrgetter from django.conf import settings from django.shortcuts import get_object_or_404 +from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.search import SearchRank, SearchVector from django.utils.translation import gettext_lazy as _ from django.core.mail import EmailMessage @@ -34,12 +35,21 @@ class HomeFeed(View): class Blogs(View): """browse the list of blogs""" - def get(self, request): + def get(self, request, category=None): """here they are""" - blogs = models.Blog.objects.filter(approved=True, active=True).order_by( - "-updateddate" - ) + if category: + + blogs = models.Blog.objects.filter(approved=True, active=True, category=category).order_by( + "-updateddate" + ) + + else: + + blogs = models.Blog.objects.filter(approved=True, active=True).order_by( + "-updateddate" + ) + for blog in blogs: blog.category_name = models.Category(blog.category).label data = {"title": "Blogs and websites", "blogs": blogs} @@ -49,18 +59,22 @@ class Blogs(View): class Conferences(View): """browse the list of conferences""" - def get(self, request): + def get(self, request, category=None): """here they are""" now = timezone.now() - cons = models.Event.objects.filter(approved=True, start_date__gte=now).order_by( - "start_date" - ) + + if category: + cons = models.Event.objects.filter(approved=True, start_date__gte=now, category=category).order_by( + "start_date" + ) + else: + cons = models.Event.objects.filter(approved=True, start_date__gte=now).order_by( + "start_date" + ) + for con in cons: con.category_name = models.Category(con.category).label - con.call_for_papers = con.cfp.all().last() - if con.call_for_papers and (con.call_for_papers.closing_date > now.date()): - date = con.call_for_papers.closing_date.strftime("%a %d %b %Y") - con.call_for_papers = f"{con.call_for_papers.name} closes {date}" + con.call_for_papers = con.cfp.filter(closing_date__gte=timezone.now()).order_by("-closing_date").last() data = {"title": "Upcoming events", "cons": cons} return render(request, "browse/events.html", data) @@ -82,9 +96,15 @@ class CallsForPapers(View): class Groups(View): """browse the list of groups""" - def get(self, request): + def get(self, request, category=None): """here they are""" - groups = models.Group.objects.filter(approved=True).order_by("name") + + if category: + groups = models.Group.objects.filter(approved=True, category=category).order_by("name") + + else: + groups = models.Group.objects.filter(approved=True).order_by("name") + for group in groups: group.category_name = models.Category(group.category).label group.reg_type = models.utils.GroupType(group.type).label @@ -188,17 +208,7 @@ class RegisterConference(View): if form.is_valid(): conf = form.save() send_email("event", conf) - cfp_form = forms.RegisterCallForPapersForm({"event": conf.id}) - data = { - "title": "Register your Call for Papers", - "form": cfp_form, - "conf_name": conf.name, - } - - return render(request, "events/cfp.html", data) - - data = {"title": "Complete blog registration", "form": form, "errors": True} - return render(request, "events/register.html", data) + return redirect("/thankyou/conference") class RegisterCallForPapers(View): @@ -292,13 +302,16 @@ class Search(View): query = request.GET.get("q") article_vector = ( - SearchVector("tags__name", weight="A") + SearchVector("tagnames", weight="A") + SearchVector("title", weight="B") + SearchVector("description", weight="C") ) articles = ( - models.Article.objects.annotate(rank=SearchRank(article_vector, query)) + models.Article.objects.annotate( + tagnames=ArrayAgg("tags__name", distinct=True), # see above: this prevents many-to-many objects like tags causing the same article to appear in the results multiple times + rank=SearchRank(article_vector, query) + ) .filter(rank__gte=0.1) .order_by("-rank") ) @@ -308,7 +321,8 @@ class Search(View): ) events = ( - models.Event.objects.filter(approved=True).annotate(rank=SearchRank(conference_vector, query)) + models.Event.objects.filter(approved=True) + .annotate(rank=SearchRank(conference_vector, query)) .filter(rank__gte=0.1) .order_by("-rank") ) @@ -318,7 +332,8 @@ class Search(View): ) cfps = ( - models.CallForPapers.objects.filter(approved=True).annotate(rank=SearchRank(cfp_vector, query)) + models.CallForPapers.objects.filter(approved=True) + .annotate(rank=SearchRank(cfp_vector, query)) .filter(rank__gte=0.1) .order_by("-rank") ) @@ -328,7 +343,8 @@ class Search(View): ) newsletters = ( - models.Newsletter.objects.filter(approved=True).annotate(rank=SearchRank(news_vector, query)) + models.Newsletter.objects.filter(approved=True) + .annotate(rank=SearchRank(news_vector, query)) .filter(rank__gte=0.1) .order_by("-rank") ) @@ -338,7 +354,8 @@ class Search(View): ) groups = ( - models.Event.objects.filter(approved=True).annotate(rank=SearchRank(group_vector, query)) + models.Group.objects.filter(approved=True) + .annotate(rank=SearchRank(group_vector, query)) .filter(rank__gte=0.1) .order_by("-rank") ) diff --git a/docker-compose.yml b/docker-compose.yml index 425c772..2255066 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,8 +11,8 @@ services: web: build: . env_file: .env - # command: python manage.py runserver 0.0.0.0:8000 - command: gunicorn --env DJANGO_SETTINGS_MODULE=ausglamr.settings ausglamr.wsgi --workers=10 --threads=4 -b 0.0.0.0:8000 + command: python manage.py runserver 0.0.0.0:8000 + # command: gunicorn --env DJANGO_SETTINGS_MODULE=ausglamr.settings ausglamr.wsgi --workers=10 --threads=4 -b 0.0.0.0:8000 volumes: - .:/app depends_on: diff --git a/glamr-dev b/glamr-dev index 30cce8c..7290dfe 100755 --- a/glamr-dev +++ b/glamr-dev @@ -30,6 +30,11 @@ case "$CMD" in announce) runweb python manage.py announce ;; + backup) + /snap/bin/docker exec -u root ausglamr_db_1 pg_dump -v -Fc -U ausglamr -d "ausglamr" -f /tmp/ausglamr_backup.dump + /snap/bin/docker cp ausglamr_db_1:/tmp/ausglamr_backup.dump /home/hugh/backups/ + mv /home/hugh/backups/ausglamr_backup.dump /home/hugh/backups/ausglamr_backup_$(date +'%a').dump + ;; black) docker compose run --rm web black ausglamr blogs ;;