another ridiculously large commit
This commit is contained in:
parent
8cc4bda231
commit
ff1d837eb6
|
@ -25,7 +25,12 @@ POSTGRES_DB="ausglamr"
|
|||
PGPORT=5432
|
||||
POSTGRES_HOST="db"
|
||||
|
||||
# mastodon
|
||||
# mastodon bot
|
||||
|
||||
MASTODON_ACCESS_TOKEN=""
|
||||
MASTODON_DOMAIN="https://example.com"
|
||||
|
||||
# filepaths for backups cron - include a leading slash
|
||||
|
||||
DOCKER_PATH="/usr/bin/docker" # example, check your path
|
||||
BACKUPS_DIR="/home/my_user/backups" # where you want your backups to be stored
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
data
|
||||
/data
|
||||
fixtures
|
||||
/static
|
||||
.env.dev
|
||||
z_README_Hugh.md
|
15
README.md
15
README.md
|
@ -12,7 +12,7 @@ A django app running on Docker. Replaces _Aus GLAM Blogs_.
|
|||
|
||||
* set up database backups (as cron jobs):
|
||||
|
||||
* `./glamr-dev backup`
|
||||
* `./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/`
|
||||
|
@ -20,10 +20,6 @@ A django app running on Docker. Replaces _Aus GLAM Blogs_.
|
|||
|
||||
* set up cron jobs for management commands as below
|
||||
|
||||
## Migrating
|
||||
|
||||
See `data/help.txt`
|
||||
|
||||
## Admin
|
||||
|
||||
Don't forget to add some Content Warnings for use by the Mastodon bot.
|
||||
|
@ -33,6 +29,7 @@ Don't forget to add some Content Warnings for use by the Mastodon bot.
|
|||
Use `glamr-dev` to make your life easier (thanks to Mouse Reeve for the inspiration):
|
||||
|
||||
* announce
|
||||
* backup
|
||||
* check_feeds
|
||||
* manage [django management command]
|
||||
* makemigrations
|
||||
|
@ -77,6 +74,8 @@ Run every 21 mins.
|
|||
|
||||
This checks all blog feeds for any new posts, and adds them to the database as long as they don't have an exclusion tag and were not published during a time the blog was suspended.
|
||||
|
||||
Also checks newsletter articles if there is a feed.
|
||||
|
||||
Run every hour.
|
||||
|
||||
### queue_announcements
|
||||
|
@ -90,3 +89,9 @@ Run daily.
|
|||
Does what you think. Creates a weekly email of the latest stuff, and send to everyone in Subscribers.
|
||||
|
||||
Run weekly.
|
||||
|
||||
### Backups
|
||||
|
||||
There is a `backup` command in `glamr-dev`. You can adjust the filepaths in your `.env` file.
|
||||
|
||||
Run daily
|
|
@ -12,13 +12,17 @@ urlpatterns = [
|
|||
re_path(r"^browse/?$", views.Browse.as_view(), name="browse"),
|
||||
re_path(r"^search/?$", views.Search.as_view(), name="search"),
|
||||
re_path(r"^help/?$", views.Help.as_view(), name="help"),
|
||||
path("feed", views.CombinedFeed(), name="feed"),
|
||||
path("blog-articles/feed", views.ArticleFeed(), name="article-feed"),
|
||||
path("events/feed", views.EventFeed(), name="event-feed"),
|
||||
path("newsletter-editions/feed", views.EditionFeed(), name="edition-feed"),
|
||||
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/<category>", views.Blogs.as_view(), name="blog-category "),
|
||||
path("blog-articles", views.Articles.as_view(), name="blog-articles"),
|
||||
path("events", views.Conferences.as_view(), name="events"),
|
||||
path("events/<category>", views.Conferences.as_view(), name="events-category"),
|
||||
path("events", views.Events.as_view(), name="events"),
|
||||
path("events/<category>", views.Events.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/<category>", views.Groups.as_view(), name="group-category"),
|
||||
|
@ -60,6 +64,4 @@ urlpatterns = [
|
|||
views.UnsubscribeEmail.as_view(),
|
||||
name="unsubscribe-email",
|
||||
),
|
||||
path("feeds/blogs", views.ArticleFeed(), name="article-feed"),
|
||||
path("feeds/events", views.EventFeed(), name="event-feed"),
|
||||
]
|
||||
|
|
|
@ -115,7 +115,7 @@ class RegisterNewsletterForm(forms.ModelForm):
|
|||
model = Newsletter
|
||||
fields = [
|
||||
"name",
|
||||
"author",
|
||||
"author_name",
|
||||
"category",
|
||||
"url",
|
||||
"feed",
|
||||
|
|
|
@ -47,6 +47,16 @@ class Command(BaseCommand):
|
|||
action="store_true",
|
||||
help="Suppress non-error messages",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-blogs",
|
||||
action="store_true",
|
||||
help="Only check blog posts",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-newsletters",
|
||||
action="store_true",
|
||||
help="Only check editions",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""check feeds and update database"""
|
||||
|
@ -56,6 +66,7 @@ class Command(BaseCommand):
|
|||
f"checking feeds at {django_timezone.localtime(django_timezone.now())}"
|
||||
)
|
||||
|
||||
if not options["newsletters"]:
|
||||
blogs = models.Blog.objects.filter(
|
||||
approved=True, suspended=False, active=True
|
||||
).all()
|
||||
|
@ -65,7 +76,8 @@ class Command(BaseCommand):
|
|||
|
||||
for article in data.entries:
|
||||
if not models.Article.objects.filter(
|
||||
Q(url=article.link) | Q(guid=getattr(article, "id", article.link))
|
||||
Q(url=article.link)
|
||||
| Q(guid=getattr(article, "id", article.link))
|
||||
).exists():
|
||||
if blog.suspension_lifted and (
|
||||
blog.suspension_lifted
|
||||
|
@ -101,9 +113,9 @@ class Command(BaseCommand):
|
|||
continue
|
||||
|
||||
if not opt_out:
|
||||
author_name = getattr(article, "author", None) or getattr(
|
||||
blog, "author", None
|
||||
)
|
||||
author_name = getattr(
|
||||
article, "author", None
|
||||
) or getattr(blog, "author", None)
|
||||
|
||||
description = (
|
||||
html.strip_tags(article.summary)
|
||||
|
@ -125,7 +137,9 @@ class Command(BaseCommand):
|
|||
author_name=author_name,
|
||||
url=article.link,
|
||||
description=description,
|
||||
updateddate=date_to_tz_aware(article.updated_parsed),
|
||||
updateddate=date_to_tz_aware(
|
||||
article.updated_parsed
|
||||
),
|
||||
blog=blog,
|
||||
pubdate=date_to_tz_aware(article.published_parsed),
|
||||
guid=getattr(article, "id", article.link),
|
||||
|
@ -156,6 +170,7 @@ class Command(BaseCommand):
|
|||
logging.info(article)
|
||||
logging.error(e)
|
||||
|
||||
if not options["blogs"]:
|
||||
newsletters = models.Newsletter.objects.filter(
|
||||
approved=True, active=True, feed__isnull=False
|
||||
).all()
|
||||
|
@ -165,10 +180,11 @@ class Command(BaseCommand):
|
|||
|
||||
for edition in data.entries:
|
||||
if not models.Edition.objects.filter(
|
||||
Q(url=edition.link) | Q(guid=getattr(edition, "id", edition.link))
|
||||
Q(url=edition.link)
|
||||
| Q(guid=getattr(edition, "id", edition.link))
|
||||
).exists():
|
||||
author_name = getattr(edition, "author", None) or getattr(
|
||||
blog, "author", None
|
||||
edition, "author", None
|
||||
)
|
||||
|
||||
description = (
|
||||
|
@ -178,9 +194,11 @@ class Command(BaseCommand):
|
|||
)
|
||||
else html.strip_tags(edition.description)
|
||||
if (
|
||||
hasattr(edition, "description") and len(edition.summary)
|
||||
hasattr(edition, "description")
|
||||
and len(edition.summary)
|
||||
)
|
||||
else html.strip_tags(edition.content[0].value)[:200] + "..."
|
||||
else html.strip_tags(edition.content[0].value)[:200]
|
||||
+ "..."
|
||||
)
|
||||
description += "..."
|
||||
|
||||
|
|
|
@ -32,14 +32,15 @@ class Command(BaseCommand):
|
|||
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)
|
||||
events = models.Event.objects.filter(approved=True, pubdate__gte=cutoff)
|
||||
cfps = models.CallForPapers.objects.filter(
|
||||
event__approved=True, closing_date__gte=timezone.now().date()
|
||||
)
|
||||
newsletters = models.Newsletter.objects.filter(
|
||||
approved=True, pub_date__gte=cutoff
|
||||
approved=True, pubdate__gte=cutoff
|
||||
)
|
||||
groups = models.Group.objects.filter(approved=True, pub_date__gte=cutoff)
|
||||
editions = models.Edition.objects.filter(pubdate__gte=cutoff)
|
||||
groups = models.Group.objects.filter(approved=True, pubdate__gte=cutoff)
|
||||
|
||||
new_blogs = ""
|
||||
for blog in blogs:
|
||||
|
@ -78,11 +79,33 @@ class Command(BaseCommand):
|
|||
|
||||
if new_articles != "":
|
||||
new_articles = (
|
||||
"<h3 style='margin-top:20px;'>New Articles</h3>"
|
||||
"<h3 style='margin-top:20px;'>New Blog Posts</h3>"
|
||||
+ new_articles
|
||||
+ "<hr/>"
|
||||
)
|
||||
|
||||
new_editions = ""
|
||||
for edition in editions:
|
||||
title_string = f"<h4><a href='{edition.url}'>{edition.title}</a></h4>"
|
||||
author_string = (
|
||||
f"<p><em>{edition.author_name}</em></p>" if edition.author_name else ""
|
||||
)
|
||||
description_string = (
|
||||
f"<p style='margin-bottom:24px;'>{edition.description}</p>"
|
||||
)
|
||||
|
||||
string_list = [title_string, author_string, description_string]
|
||||
string = "".join(string_list)
|
||||
|
||||
new_editions = new_editions + string
|
||||
|
||||
if new_editions != "":
|
||||
new_editions = (
|
||||
"<h3 style='margin-top:20px;'>New Newsletter Editions</h3>"
|
||||
+ new_editions
|
||||
+ "<hr/>"
|
||||
)
|
||||
|
||||
coming_events = ""
|
||||
for event in events:
|
||||
s_date = event.start_date
|
||||
|
@ -195,6 +218,7 @@ class Command(BaseCommand):
|
|||
subject = f"{emoji} Fresh Aus GLAMR updates for the week of {dt.day} {dt:%B} {dt.year}"
|
||||
sections = [
|
||||
new_articles,
|
||||
new_editions,
|
||||
new_blogs,
|
||||
new_newsletters,
|
||||
new_groups,
|
||||
|
@ -203,7 +227,7 @@ class Command(BaseCommand):
|
|||
]
|
||||
body = "".join(sections)
|
||||
if body == "":
|
||||
body = "<p>No new updates this week.</p><p>Why not spend the time you would have been reading, publishing your own blog post instead?</p>"
|
||||
body = "<p>No new updates this week.</p><p>Why not spend some time publishing your own blog post instead?</p>"
|
||||
|
||||
for subscriber in subscribers:
|
||||
opt_out = f"https://{settings.DOMAIN}/unsubscribe-email/{subscriber.token}/{subscriber.id}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.7 on 2024-01-21 04:31
|
||||
# Generated by Django 4.2.7 on 2024-01-22 08:13
|
||||
|
||||
import blogs.models.blog
|
||||
from django.db import migrations, models
|
||||
|
@ -50,6 +50,10 @@ class Migration(migrations.Migration):
|
|||
("url", models.URLField(max_length=2000, unique=True)),
|
||||
("description", models.TextField(blank=True, null=True)),
|
||||
("updateddate", models.DateTimeField()),
|
||||
(
|
||||
"pubdate",
|
||||
models.DateTimeField(default=django.utils.timezone.now, null=True),
|
||||
),
|
||||
("feed", models.URLField(max_length=2000)),
|
||||
(
|
||||
"category",
|
||||
|
@ -143,7 +147,7 @@ class Migration(migrations.Migration):
|
|||
"description",
|
||||
models.TextField(blank=True, max_length=250, null=True),
|
||||
),
|
||||
("pub_date", models.DateTimeField()),
|
||||
("pubdate", models.DateTimeField()),
|
||||
("start_date", models.DateField()),
|
||||
(
|
||||
"announcements",
|
||||
|
@ -219,7 +223,10 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
("announced", models.BooleanField(default=False)),
|
||||
("approved", models.BooleanField(default=False)),
|
||||
("pub_date", models.DateTimeField(default=None, null=True)),
|
||||
(
|
||||
"pubdate",
|
||||
models.DateTimeField(default=django.utils.timezone.now, null=True),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -235,7 +242,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100)),
|
||||
("author", models.CharField(max_length=100)),
|
||||
("author_name", models.CharField(max_length=100)),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
|
@ -275,7 +282,7 @@ class Migration(migrations.Migration):
|
|||
("active", models.BooleanField(default=True, null=True)),
|
||||
("failing", models.BooleanField(blank=True, default=False, null=True)),
|
||||
("updateddate", models.DateTimeField()),
|
||||
("pub_date", models.DateTimeField(default=None, null=True)),
|
||||
("pubdate", models.DateTimeField(default=None, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -350,7 +357,10 @@ class Migration(migrations.Migration):
|
|||
("url", models.URLField(max_length=2000, unique=True)),
|
||||
("description", models.TextField(blank=True, null=True)),
|
||||
("updateddate", models.DateTimeField()),
|
||||
("pubdate", models.DateTimeField()),
|
||||
(
|
||||
"pubdate",
|
||||
models.DateTimeField(default=django.utils.timezone.now, null=True),
|
||||
),
|
||||
("guid", models.CharField(max_length=2000)),
|
||||
(
|
||||
"newsletter",
|
||||
|
@ -376,7 +386,10 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
("name", models.CharField(max_length=100)),
|
||||
("details", models.TextField(blank=True, max_length=250, null=True)),
|
||||
("pub_date", models.DateTimeField(default=None, null=True)),
|
||||
(
|
||||
"pubdate",
|
||||
models.DateTimeField(default=django.utils.timezone.now, null=True),
|
||||
),
|
||||
("opening_date", models.DateField()),
|
||||
("closing_date", models.DateField()),
|
||||
("announcements", models.IntegerField(default=0, null=True)),
|
||||
|
@ -411,7 +424,10 @@ class Migration(migrations.Migration):
|
|||
("url", models.URLField(max_length=2000, unique=True)),
|
||||
("description", models.TextField(blank=True, null=True)),
|
||||
("updateddate", models.DateTimeField()),
|
||||
("pubdate", models.DateTimeField()),
|
||||
(
|
||||
"pubdate",
|
||||
models.DateTimeField(default=django.utils.timezone.now, null=True),
|
||||
),
|
||||
("guid", models.CharField(max_length=2000)),
|
||||
(
|
||||
"blog",
|
||||
|
|
|
@ -29,6 +29,7 @@ class BlogData(models.Model):
|
|||
url = models.URLField(max_length=2000, unique=True)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
updateddate = models.DateTimeField()
|
||||
pubdate = models.DateTimeField(null=True, default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
"""This is an abstract model for common data"""
|
||||
|
@ -107,7 +108,6 @@ class Article(BlogData):
|
|||
"""A blog post"""
|
||||
|
||||
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")
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class Event(models.Model):
|
|||
category = models.CharField(choices=Category.choices, max_length=4)
|
||||
url = models.URLField(max_length=400, unique=True)
|
||||
description = models.TextField(null=True, blank=True, max_length=250)
|
||||
pub_date = models.DateTimeField() # for RSS feed
|
||||
pubdate = models.DateTimeField() # for RSS feed
|
||||
start_date = models.DateField()
|
||||
announcements = models.IntegerField(null=True, blank=True, default=0)
|
||||
activitypub_account_name = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
@ -21,8 +21,8 @@ class Event(models.Model):
|
|||
approved = models.BooleanField(default=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pub_date:
|
||||
self.pub_date = timezone.now()
|
||||
if not self.pubdate:
|
||||
self.pubdate = timezone.now()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -58,7 +58,7 @@ class CallForPapers(models.Model):
|
|||
max_length=100
|
||||
) # "Call for papers", "call for participation" etc
|
||||
details = models.TextField(null=True, blank=True, max_length=250)
|
||||
pub_date = models.DateTimeField(null=True, default=None)
|
||||
pubdate = models.DateTimeField(null=True, default=timezone.now)
|
||||
opening_date = models.DateField()
|
||||
closing_date = models.DateField()
|
||||
announcements = models.IntegerField(null=True, default=0)
|
||||
|
@ -77,9 +77,3 @@ class CallForPapers(models.Model):
|
|||
Announcement.objects.create(status=status)
|
||||
self.announcements = self.announcements + 1
|
||||
super().save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""save a CFP with pub_date"""
|
||||
if not self.pub_date:
|
||||
self.pub_date = timezone.now()
|
||||
super().save(*args, **kwargs)
|
||||
|
|
|
@ -16,9 +16,10 @@ class Group(models.Model):
|
|||
registration_url = models.URLField(max_length=400, unique=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)
|
||||
pub_date = models.DateTimeField(null=True, default=None)
|
||||
pubdate = models.DateTimeField(null=True, default=timezone.now)
|
||||
|
||||
def announce(self):
|
||||
"""create a group announcement"""
|
||||
|
@ -30,8 +31,3 @@ class Group(models.Model):
|
|||
Announcement.objects.create(status=status)
|
||||
self.announced = True
|
||||
super().save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pub_date:
|
||||
self.pub_date = timezone.now()
|
||||
super().save(*args, **kwargs)
|
||||
|
|
|
@ -10,7 +10,7 @@ class Newsletter(models.Model):
|
|||
"""a newsletter"""
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
author = models.CharField(max_length=100)
|
||||
author_name = models.CharField(max_length=100)
|
||||
category = models.CharField(choices=Category.choices, max_length=4)
|
||||
url = models.URLField(max_length=400, unique=True)
|
||||
feed = models.URLField(max_length=1000, unique=True, blank=True, null=True)
|
||||
|
@ -24,17 +24,21 @@ class Newsletter(models.Model):
|
|||
failing = models.BooleanField(default=False, blank=True, null=True)
|
||||
|
||||
updateddate = models.DateTimeField()
|
||||
pub_date = models.DateTimeField(null=True, default=None)
|
||||
pubdate = models.DateTimeField(null=True, default=None)
|
||||
|
||||
def __str__(self):
|
||||
"""display for admin dropdowns"""
|
||||
return self.name
|
||||
|
||||
def announce(self):
|
||||
"""create a event announcement"""
|
||||
|
||||
category = Category(self.category).label
|
||||
name = self.name
|
||||
name = self.author_name
|
||||
if self.activitypub_account_name:
|
||||
name = f"{self.name} ({self.activitypub_account_name})"
|
||||
name = f"{self.author_name} ({self.activitypub_account_name})"
|
||||
|
||||
status = f"{name} is a newsletter about {category} from {self.author}. Check it out:\n\n{self.url}"
|
||||
status = f"{self.name} is a newsletter about {category} from {name}. Check it out:\n\n{self.url}"
|
||||
|
||||
Announcement.objects.create(status=status)
|
||||
self.announced = True
|
||||
|
@ -74,5 +78,21 @@ class Edition(models.Model):
|
|||
newsletter = models.ForeignKey(
|
||||
Newsletter, on_delete=models.CASCADE, related_name="editions"
|
||||
)
|
||||
pubdate = models.DateTimeField()
|
||||
pubdate = models.DateTimeField(null=True, default=timezone.now)
|
||||
guid = models.CharField(max_length=2000)
|
||||
|
||||
def announce(self):
|
||||
"""queue an edition announcement"""
|
||||
|
||||
author = self.newsletter.activitypub_account_name or self.author_name
|
||||
|
||||
if self.newsletter.activitypub_account_name:
|
||||
author = f"{self.newsletter.activitypub_account_name} - "
|
||||
elif self.author_name:
|
||||
author = f"{self.author_name} - "
|
||||
else:
|
||||
author = ""
|
||||
|
||||
status = f"📬 {self.title} ({author}{self.newsletter.name})\n\n{self.url}"
|
||||
|
||||
Announcement.objects.create(status=status)
|
||||
|
|
|
@ -199,7 +199,7 @@ footer .left p {
|
|||
|
||||
.subscribe .pop,
|
||||
.contribute .pop {
|
||||
min-height: 48rem;
|
||||
min-height: 44rem;
|
||||
}
|
||||
|
||||
.contribute {
|
||||
|
@ -209,9 +209,6 @@ footer .left p {
|
|||
grid-template-columns: 33% 33% 33%;
|
||||
}
|
||||
|
||||
/* .contribute .pop {
|
||||
min-height: 28rem;
|
||||
} */
|
||||
|
||||
.pop {
|
||||
margin-top: 2em;
|
||||
|
@ -228,12 +225,9 @@ footer .left p {
|
|||
grid-template-columns: 50% 50%;
|
||||
}
|
||||
|
||||
.subscribe .pop {
|
||||
min-height: 34rem;
|
||||
}
|
||||
|
||||
.subscribe .pop,
|
||||
.contribute .pop {
|
||||
min-height: 30rem;
|
||||
min-height: 33rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -336,7 +330,6 @@ footer .left p {
|
|||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #aaa;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
|
15
blogs/static/favicon.svg
Normal file
15
blogs/static/favicon.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Layer1">
|
||||
<rect x="-1.793" y="0.753" width="1002.14" height="1000.3"/>
|
||||
</g>
|
||||
<g transform="matrix(0.923477,0,0,0.923477,82.0351,29.5455)">
|
||||
<g transform="matrix(0.909938,0,0,0.909938,48.7165,46.9138)">
|
||||
<text x="1.567px" y="999.474px" style="font-family:'Helsinki';font-size:1395.51px;fill:white;">A</text>
|
||||
</g>
|
||||
<g transform="matrix(0.909938,0,0,0.909938,21.5737,36.7307)">
|
||||
<text x="1.567px" y="999.474px" style="font-family:'Helsinki';font-size:1395.51px;fill:rgb(255,182,193);">A</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -1,5 +1,9 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block feed %}
|
||||
<link href="blog-articles/feed" type="application/atom+xml" rel="alternate" title="Aus GLAMR Blogs" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% for post in latest %}
|
||||
<div class="card">
|
||||
|
@ -25,5 +29,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>Oh no! There are no articles available. Try checking out <a href="{% url 'newsletter-editions' %}">some newsletter editions</a>.</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -1,5 +1,9 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block feed %}
|
||||
<link href="blog-articles/feed" type="application/atom+xml" rel="alternate" title="Aus GLAMR Blogs" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="buttons">
|
||||
<a href="{% url 'blog-articles' %}"><button class="button button-primary button-first">Latest posts</button></a>
|
||||
|
@ -23,7 +27,7 @@
|
|||
</div>
|
||||
<hr/>
|
||||
{% empty %}
|
||||
<p>Oh no! There are no blogs currently registered. Try checking out <a href="{% url 'newsletters' %}">some newsletters</a>.</p>
|
||||
<p>Oh no! There are no blogs currently registered{% if category %} under '{{category}}'{% endif %}. Try checking out <a href="{% url 'newsletters' %}">some newsletters</a>.</p>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,7 +1,13 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% block feed %}
|
||||
<link href="events/feed" type="application/atom+xml" rel="alternate" title="Aus GLAMR Events" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="buttons">
|
||||
<a href="{% url 'register-cfp' %}"><button class="button">Add a CFP</button></a>
|
||||
</div>
|
||||
<div class="listing l-three header">
|
||||
<span>Details</span>
|
||||
<span>Event</span>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block feed %}
|
||||
<link href="newsletter-editions/feed" type="application/atom+xml" rel="alternate" title="Aus GLAMR Newsletters" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% for edition in latest %}
|
||||
<div class="card">
|
||||
|
@ -9,7 +13,7 @@
|
|||
{% if edition.author_name %}
|
||||
<span class="author_name">{{ edition.author_name }}</span> |
|
||||
{% endif %}
|
||||
<span class="blog_title">{{ edition.newsletter.title }}</span>
|
||||
<span class="blog_title">{{ edition.newsletter.name }}</span>
|
||||
</p>
|
||||
</div>
|
||||
{% if edition.description %}
|
||||
|
@ -20,5 +24,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>Oh no! There are no editions available. Try checking out <a href="{% url 'blog-articles' %}">some blog posts</a>.</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -1,9 +1,13 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block feed %}
|
||||
<link href="events/feed" type="application/atom+xml" rel="alternate" title="Aus GLAMR Events" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="buttons">
|
||||
<a href="{% url 'cfps' %}"><button class="button button-primary button-first">Open CFPs</button></a>
|
||||
<a href="{% url 'cfps' %}"><button class="button button-primary button-first">Current CFPs</button></a>
|
||||
<a href="{% url 'register-event' %}"><button class="button">Add an Event</button></a>
|
||||
<a href="{% url 'register-cfp' %}"><button class="button">Add a CFP</button></a>
|
||||
</div>
|
||||
|
@ -29,7 +33,7 @@
|
|||
</div>
|
||||
<hr/>
|
||||
{% empty %}
|
||||
<p>Oh no! There are no events currently registered. Try checking out <a href="{% url 'blogs' %}">some blogs</a>.</p>
|
||||
<p>Oh no! There are no events currently registered{% if category %} under '{{category}}'{% endif %}. Try checking out <a href="{% url 'blogs' %}">some blogs</a>.</p>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,5 +1,9 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block feed %}
|
||||
<link href="newsletter-editions/feed" type="application/atom+xml" rel="alternate" title="Aus GLAMR Newsletters" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="buttons">
|
||||
<a href="{% url 'newsletter-editions' %}"><button class="button button-primary button-first">Latest editions</button></a>
|
||||
|
@ -19,11 +23,11 @@
|
|||
<a href="{{pub.feed}}">{% include 'utils/rss-img.html' %}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span>{{pub.author}}</span>
|
||||
<span>{{pub.author_name}}</span>
|
||||
<span><span class="badge badge_{{pub.category}}"><a href="/newsletters/{{pub.category}}">{{pub.category_name}}</a></span></span>
|
||||
</div>
|
||||
<hr/>
|
||||
{% empty %}
|
||||
<p>Oh no! There are no newsletter currently registered. Try checking out <a href="{% url 'blogs' %}">some blogs</a>.</p>
|
||||
<p>Oh no! There are no newsletter currently registered{% if category %} under '{{category}}'{% endif %}. Try checking out <a href="{% url 'blogs' %}">some blogs</a>.</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -26,20 +26,18 @@
|
|||
<a class="button" href="{% url 'register-group' %}">Register group</a>
|
||||
</section>
|
||||
|
||||
<section class="pop">
|
||||
<h2>Register an event</h2>
|
||||
<p>Conference, convention, seminar, workshop, talk, meet-up...
|
||||
</br>Whatever you call it, you know what we mean.</p>
|
||||
<a class="button" href="{% url 'register-event' %}">Register event</a>
|
||||
</section>
|
||||
|
||||
<section class="pop">
|
||||
<h2>Register a newsletter</h2>
|
||||
<p>Whether it uses Ghost, Write.as, Mailchimp or something else, if it's an email newsletter with some kind of GLAMR focus you can register it here.</p>
|
||||
<a class="button" href="{% url 'register-newsletter' %}">Register newsletter</a>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="pop">
|
||||
<h2>Register an event</h2>
|
||||
<p>Conference, convention, seminar, workshop, talk, meet-up...
|
||||
</br>Whatever you call it, you know what we mean.</p>
|
||||
<a class="button" href="{% url 'register-event' %}">Register event</a>
|
||||
</section>
|
||||
|
||||
<section class="pop">
|
||||
<h2>Register or update a Call for Papers</h2>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
{% with 'aus-glam-blogs' as anchor %}
|
||||
{% url 'help' as the_url %}
|
||||
<h4 id="{{anchor}}">What happened to Aus GLAM Blogs?</h4><a href="{{ the_url }}/#{{anchor}}">🔗</a>
|
||||
<p><em>Aus GLAM Blogs</em> was focussed on blogs specifically. <em>Aus GLAMR</em> is the successor, now including more GLAMR content. All the blog articles from the original site have been migrated and will be inluded in your search results.</p>
|
||||
<p><em>Aus GLAM Blogs</em> was focussed on blogs specifically. <em>Aus GLAMR</em> is the successor, now including more GLAMR content. All the blog articles from the original site have been migrated and will be included in your search results.</p>
|
||||
{% endwith %}
|
||||
|
||||
{% with 'pocket' as anchor %}
|
||||
|
@ -44,6 +44,14 @@
|
|||
<h4 id="{{anchor}}">Activitypub account name!!???</h4><a href="{{ the_url }}/#{{anchor}}">🔗</a>
|
||||
<p>ActivityPub is the protocol used by Mastodon, BlueSky, BookWyrm, Threads, and other "fediverse" social media. If you include an account name, it will be mentioned when your registered thing is announced by the AusGLAMR Mastodon bot. If you'd like to join the "fediverse", try <a href="https://ausglam.space">Aus GLAM Space</a>.</p>
|
||||
{% endwith %}
|
||||
|
||||
{% with 'aus' as anchor %}
|
||||
{% url 'help' as the_url %}
|
||||
<h4 id="{{anchor}}">Why 'Aus' GLAMR?</h4><a href="{{ the_url }}/#{{anchor}}">🔗</a>
|
||||
<p>The "Aus" is short for "Australasia". This is kind of a cheat - GLAMR content from Australia, Aotearoa/New Zealand, and our neighbours is welcome. I considered calling it "ANZ GLAMR" but I didn't want to do the Australian thing of pretending that Aotearoa/New Zealand is practically just another state of Australia with no cultural or institutional differences.</p>
|
||||
<p>Opening it up to the whole world would likely result in the same thing that happens on every other English-language website: an assumed default of United States of America context, and a drowning out of anything else.</p>
|
||||
<p>In short this aggregator is made by an Australian in Australia with that context in mind. Kiwis and other South-West Pacific neighbours are super welcome to contribute, but they also have their own context and may prefer their own thing. I can't speak for them.</p>
|
||||
{% endwith %}
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
|
@ -1,12 +1,16 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block feed %}
|
||||
<link href="feed" type="application/atom+xml" rel="alternate" title="Aus GLAMR" />
|
||||
{% endblock %}
|
||||
|
||||
{% block intro %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<p><em>Aus GLAMR</em> is the place to find out what's happening in the Australasian cultural memory professions. Whether you work in galleries, libraries, archives, museums, or records, you'll find the latest blog posts, conferences and events, newsletters, and discussion groups here!</p>
|
||||
<p>Browse one of the lists, <a href="{% url 'search' %}">search by keywords</a>, or <a href="{% url 'contribute' %}">contribute by registering</a> your blog, event, group or newsletter.</p>
|
||||
<p>You can stay up to date by following the Mastodon bot or subscribing to email updates or to one of the RSS feeds - check out the <a href="{% url 'subscribe' %}">subscribe page</a>.</p>
|
||||
<p>You can stay up to date by subscribing via the Mastodon bot, email updates or one of the RSS feeds - check out the <a href="{% url 'subscribe' %}">subscribe page</a>.</p>
|
||||
<p>Please note that whilst off-topic or egregiously offensive content may be refused or removed, inclusion on this site does not imply endorsement by Hugh or newCardigan of the content of any particular listed publication or event.</p>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -11,6 +11,7 @@
|
|||
<link rel="stylesheet" href="{% static 'css/normalize.css' %}" type="text/css" />
|
||||
<link rel="stylesheet" href="{% static 'css/skeleton.css' %}" type="text/css" />
|
||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}" type="text/css" />
|
||||
{% block feed %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</hgroup>
|
||||
<p>Get a weekly update listing the latest blog posts, open calls for papers, and upcoming events.</p>
|
||||
<div>
|
||||
<a class="button" href="{% url 'subscribe-email' %}">Subscribe via email</a>
|
||||
<a class="button button-primary" href="{% url 'subscribe-email' %}">Subscribe</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -23,13 +23,13 @@
|
|||
<hgroup>
|
||||
<h3>Subscribe via ActivityPub</h3>
|
||||
</hgroup>
|
||||
<p>Follow the <code class="code">@blogs@ausglam.space</code> bot to see an announcement every time a new blog post , event, or call for papers is published.</p>
|
||||
<p>Follow <code class="code">@ausglamr@ausglam.space</code> to see an announcement every time something is added.</p>
|
||||
<form method="post" action="{% url 'subscribe' %}">
|
||||
{% csrf_token %}
|
||||
<label for="username">Enter address you want to follow from</label>
|
||||
{{ form.errors.username }}
|
||||
<input type="text" name="username" placeholder="@user@example.social">
|
||||
<button class="button u-pull-right" type="submit">Follow</button>
|
||||
<button class="button button-primary u-pull-right" type="submit">Follow</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
|
@ -37,11 +37,12 @@
|
|||
<hgroup>
|
||||
<h3>Subscribe via RSS</h3>
|
||||
</hgroup>
|
||||
<p>Subscribe to the blogs feed in your favourite RSS reader to get a combined feed with every new article.</p>
|
||||
<p>Or subscribe to the events feed to be the first to know about upcoming events.</p>
|
||||
<p>Ok technically it's Atom. Subscribe to the feed to stay up to date using your favourite feed reader.</p>
|
||||
<div>
|
||||
<a class="button" href="{% url 'article-feed' %}">blog posts</a>
|
||||
<a class="button" href="{% url 'event-feed' %}">events</a>
|
||||
<a class="button" href="{% url 'article-feed' %}">blog posts</a><br/>
|
||||
<a class="button" href="{% url 'event-feed' %}">events & cfps</a><br/>
|
||||
<a class="button" href="{% url 'edition-feed' %}">newsletter editions</a><br/>
|
||||
<a class="button button-primary" href="{% url 'feed' %}">everything</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
11
blogs/tests/data/example.html
Normal file
11
blogs/tests/data/example.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>My test website with no RSS feed</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello</p>
|
||||
</body>
|
||||
</html>
|
14
blogs/tests/data/good-example.html
Normal file
14
blogs/tests/data/good-example.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="https://test.test/rss.xml">
|
||||
<title>My test website with an RSS feed</title>
|
||||
<meta name="author" content="Testy McTestface">
|
||||
<meta name="description" content="My cool website">
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello</p>
|
||||
</body>
|
||||
</html>
|
12
blogs/tests/data/partial-example.html
Normal file
12
blogs/tests/data/partial-example.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="https://test.test/rss.xml">
|
||||
<title>My test website with an RSS feed</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello</p>
|
||||
</body>
|
||||
</html>
|
|
@ -33,6 +33,27 @@ class FeedParserItemMock(object):
|
|||
self.id = id
|
||||
|
||||
|
||||
class FeedParserEditionMock(object):
|
||||
title = ""
|
||||
author = ""
|
||||
link = ""
|
||||
summary = ""
|
||||
updated_parsed = ((),)
|
||||
published_parsed = ((),)
|
||||
id = ""
|
||||
|
||||
def __init__(
|
||||
self, title, author, link, summary, updated_parsed, published_parsed, id
|
||||
):
|
||||
self.title = title
|
||||
self.author
|
||||
self.link = link
|
||||
self.summary = summary
|
||||
self.updated_parsed = updated_parsed
|
||||
self.published_parsed = published_parsed
|
||||
self.id = id
|
||||
|
||||
|
||||
class FeedParserTagMock(object):
|
||||
term = ""
|
||||
|
||||
|
@ -62,6 +83,14 @@ class CommandsTestCase(TestCase):
|
|||
suspended=False,
|
||||
)
|
||||
|
||||
models.Newsletter.objects.create(
|
||||
name="My awesome newsletter",
|
||||
url="https://test.news",
|
||||
feed="https://test.news/feed",
|
||||
category="ARC",
|
||||
approved=True,
|
||||
)
|
||||
|
||||
tag_one = FeedParserTagMock(term="testing")
|
||||
tag_two = FeedParserTagMock(term="python")
|
||||
tag_three = FeedParserTagMock(term="notglam")
|
||||
|
@ -113,15 +142,26 @@ class CommandsTestCase(TestCase):
|
|||
id="333",
|
||||
)
|
||||
|
||||
edition = FeedParserEditionMock(
|
||||
title="My amazing newsletter edition",
|
||||
author="Hugh Rundle",
|
||||
link="https://news.letter/1",
|
||||
summary="A summary of my edition",
|
||||
updated_parsed=updated_parsed,
|
||||
published_parsed=published_parsed,
|
||||
id="1",
|
||||
)
|
||||
|
||||
self.feedparser = FeedParserMock(entries=[article])
|
||||
self.feedparser_old = FeedParserMock(entries=[article_three])
|
||||
self.feedparser_exclude = FeedParserMock(entries=[article_two])
|
||||
self.feedparser_new = FeedParserMock(entries=[article_four])
|
||||
self.feedparser_edition = FeedParserMock(entries=[edition])
|
||||
|
||||
def test_check_feeds(self):
|
||||
"""test parse a feed for basic blog info"""
|
||||
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
self.assertEqual(models.Article.objects.count(), 0)
|
||||
|
@ -135,13 +175,31 @@ class CommandsTestCase(TestCase):
|
|||
article = models.Article.objects.all().first()
|
||||
self.assertEqual(article.title, "My amazing blog post")
|
||||
|
||||
# should be announced
|
||||
# should be set to be announced
|
||||
self.assertEqual(models.Announcement.objects.count(), 1)
|
||||
|
||||
def test_check_edition_feeds(self):
|
||||
"""test parse a feed for newsletter edition info"""
|
||||
|
||||
args = {"-q": True, "-newsletters": True}
|
||||
opts = {}
|
||||
|
||||
self.assertEqual(models.Edition.objects.count(), 0)
|
||||
|
||||
with patch("feedparser.parse", return_value=self.feedparser_edition):
|
||||
value = call_command("check_feeds", *args, **opts)
|
||||
|
||||
self.assertEqual(models.Edition.objects.count(), 1)
|
||||
edition = models.Edition.objects.all().first()
|
||||
self.assertEqual(edition.title, "My amazing newsletter edition")
|
||||
|
||||
# should be set to be announced
|
||||
self.assertEqual(models.Announcement.objects.count(), 1)
|
||||
|
||||
def test_check_feeds_duplicate(self):
|
||||
"""test we do not ingest the same post twice"""
|
||||
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
self.assertEqual(models.Article.objects.count(), 0)
|
||||
|
@ -164,7 +222,7 @@ class CommandsTestCase(TestCase):
|
|||
def test_check_feeds_new(self):
|
||||
"""test we ingest new post if id is different"""
|
||||
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
self.assertEqual(models.Article.objects.count(), 0)
|
||||
|
@ -189,7 +247,7 @@ class CommandsTestCase(TestCase):
|
|||
def test_check_feeds_old_post(self):
|
||||
"""test parse a feed with a post older than a week"""
|
||||
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
self.assertEqual(models.Article.objects.count(), 0)
|
||||
|
@ -212,7 +270,7 @@ class CommandsTestCase(TestCase):
|
|||
self.assertEqual(models.Tag.objects.count(), 0)
|
||||
|
||||
with patch("feedparser.parse", return_value=self.feedparser_exclude):
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
value = call_command("check_feeds", *args, **opts)
|
||||
|
@ -230,7 +288,7 @@ class CommandsTestCase(TestCase):
|
|||
self.blog.save()
|
||||
|
||||
with patch("feedparser.parse", return_value=self.feedparser):
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
value = call_command("check_feeds", *args, **opts)
|
||||
|
@ -248,7 +306,7 @@ class CommandsTestCase(TestCase):
|
|||
self.blog.save()
|
||||
|
||||
with patch("feedparser.parse", return_value=self.feedparser):
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
value = call_command("check_feeds", *args, **opts)
|
||||
|
@ -266,7 +324,7 @@ class CommandsTestCase(TestCase):
|
|||
self.blog.save()
|
||||
|
||||
with patch("feedparser.parse", return_value=self.feedparser):
|
||||
args = {"-q": True}
|
||||
args = {"-q": True, "-blogs": True}
|
||||
opts = {}
|
||||
|
||||
value = call_command("check_feeds", *args, **opts)
|
||||
|
|
|
@ -139,7 +139,7 @@ class NewsletterTestCase(TestCase):
|
|||
"""set up test newsletter"""
|
||||
self.news = models.Newsletter.objects.create(
|
||||
name="Awesome news",
|
||||
author="Hugh",
|
||||
author_name="Hugh",
|
||||
url="https://test.com",
|
||||
category="ARC",
|
||||
)
|
||||
|
|
|
@ -11,12 +11,12 @@ from blogs import models, utilities
|
|||
class FeedParserFeedMock(object):
|
||||
title = ""
|
||||
author = ""
|
||||
summary = ""
|
||||
subtitle = ""
|
||||
|
||||
def __init__(self, title, author, summary):
|
||||
def __init__(self, title, author, subtitle):
|
||||
self.title = title
|
||||
self.author = author
|
||||
self.summary = summary
|
||||
self.subtitle = subtitle
|
||||
|
||||
|
||||
class FeedParserMock(object):
|
||||
|
@ -48,11 +48,11 @@ class UtilityTests(TestCase):
|
|||
feed = FeedParserFeedMock(
|
||||
title="My amazing blog",
|
||||
author="Hugh Rundle",
|
||||
summary="A short summary of my blog",
|
||||
subtitle="A short summary of my blog",
|
||||
)
|
||||
|
||||
feed_partial = FeedParserFeedMock(
|
||||
title="My amazing blog", author=None, summary=None
|
||||
title="My amazing blog", author=None, subtitle=None
|
||||
)
|
||||
|
||||
self.feedparser = FeedParserMock(feed=feed)
|
||||
|
|
|
@ -202,7 +202,7 @@ class PublicTests(TestCase):
|
|||
view = views.RegisterNewsletter.as_view()
|
||||
form = forms.RegisterNewsletterForm()
|
||||
form.data["name"] = "My newsletter"
|
||||
form.data["author"] = "Bob Bobson"
|
||||
form.data["author_name"] = "Bob Bobson"
|
||||
form.data["url"] = "https://www.example.com"
|
||||
form.data["category"] = "LIB"
|
||||
|
||||
|
|
|
@ -22,7 +22,9 @@ def get_feed_info(feed):
|
|||
blog["feed"] = feed
|
||||
blog["title"] = getattr(b.feed, "title", "")
|
||||
blog["author_name"] = getattr(b.feed, "author", None)
|
||||
blog["description"] = getattr(b.feed, "subtitle", None) # summary for a FEED is "subtitle"
|
||||
blog["description"] = getattr(
|
||||
b.feed, "subtitle", None
|
||||
) # summary for a FEED is "subtitle"
|
||||
|
||||
return blog
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from .public import (
|
|||
Browse,
|
||||
Contribute,
|
||||
Contact,
|
||||
Conferences,
|
||||
Events,
|
||||
ConfirmEmail,
|
||||
CallsForPapers,
|
||||
Editions,
|
||||
|
@ -27,4 +27,4 @@ from .public import (
|
|||
UnsubscribeEmail,
|
||||
)
|
||||
|
||||
from .feeds import ArticleFeed, EventFeed
|
||||
from .feeds import ArticleFeed, EditionFeed, EventFeed, CombinedFeed
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
"""rss feeds"""
|
||||
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from itertools import chain
|
||||
from operator import attrgetter
|
||||
|
||||
from blogs.models.blog import Article
|
||||
from blogs.models.event import Event
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.utils.feedgenerator import Atom1Feed
|
||||
|
||||
|
||||
from blogs import models
|
||||
|
||||
# pylint: disable=R6301
|
||||
|
||||
|
@ -12,13 +17,17 @@ from blogs.models.event import Event
|
|||
class ArticleFeed(Feed):
|
||||
"""Combined RSS feed for all the articles"""
|
||||
|
||||
title = "Aus GLAMR Blogs"
|
||||
link = "/feeds/blogs"
|
||||
feed_type = Atom1Feed
|
||||
link = "/blog-articles"
|
||||
feed_url = "/blog-articles/feed"
|
||||
feed_guid = f"https://{settings.DOMAIN}/blog-articles/feed"
|
||||
|
||||
title = "Aus GLAMR Blog articles"
|
||||
description = "Posts from Australasian Galleries, Libraries, Archives, Museums, Records and associated blogs"
|
||||
|
||||
def items(self):
|
||||
"""each article in the feed"""
|
||||
return Article.objects.order_by("-pubdate")[:20]
|
||||
return models.Article.objects.order_by("-pubdate")[:20]
|
||||
|
||||
def item_title(self, item):
|
||||
"""article title"""
|
||||
|
@ -32,10 +41,18 @@ class ArticleFeed(Feed):
|
|||
"""article author"""
|
||||
return item.author_name
|
||||
|
||||
def item_link(self, item):
|
||||
"""item url"""
|
||||
return item.url
|
||||
|
||||
def item_pubdate(self, item):
|
||||
"""article publication date"""
|
||||
return item.pubdate
|
||||
|
||||
def item_updateddate(self, item):
|
||||
"""updated date"""
|
||||
return item.updateddate
|
||||
|
||||
def item_categories(self, item):
|
||||
"""article tags"""
|
||||
categories = []
|
||||
|
@ -45,32 +62,203 @@ class ArticleFeed(Feed):
|
|||
|
||||
|
||||
class EventFeed(Feed):
|
||||
"""Combined RSS feed for all the articles"""
|
||||
"""Combined feed for all events and calls for papers"""
|
||||
|
||||
feed_type = Atom1Feed
|
||||
link = "/events"
|
||||
feed_url = "/events/feed"
|
||||
feed_guid = f"https://{settings.DOMAIN}/events/feed"
|
||||
|
||||
title = "Aus GLAMR events"
|
||||
link = "/feeds/events"
|
||||
description = "Australasian events for Galleries, Libraries, Archives, Museums, Records workers"
|
||||
|
||||
def items(self):
|
||||
"""event items for the feed"""
|
||||
return Event.objects.order_by("-start_date")[:20]
|
||||
"""event and CFP items for the feed"""
|
||||
|
||||
events = models.Event.objects.filter(approved=True)
|
||||
cfps = models.CallForPapers.objects.all()
|
||||
|
||||
result_list = sorted(
|
||||
chain(events, cfps),
|
||||
key=attrgetter("pubdate"),
|
||||
reverse=True,
|
||||
)
|
||||
return result_list[:20]
|
||||
|
||||
def item_title(self, item):
|
||||
"""event name"""
|
||||
date = item.start_date.strftime("%d %b %Y")
|
||||
return f"{item.name} ({date})"
|
||||
"""event or CFP name"""
|
||||
return item.name
|
||||
|
||||
def item_description(self, item):
|
||||
"""event description"""
|
||||
return item.description
|
||||
"""description or details"""
|
||||
return (
|
||||
item.description
|
||||
if hasattr(item, "description")
|
||||
else item.details
|
||||
if hasattr(item, "details")
|
||||
else None
|
||||
)
|
||||
|
||||
def item_link(self, item):
|
||||
"""item url"""
|
||||
return item.url if hasattr(item, "url") else item.event.url
|
||||
|
||||
def item_pubdate(self, item):
|
||||
"""date event was registered"""
|
||||
return item.pub_date
|
||||
"""date event/CFP was registered"""
|
||||
return item.pubdate
|
||||
|
||||
def item_categories(self, item):
|
||||
"""event GLAMR category"""
|
||||
return [_(item.category)]
|
||||
if hasattr(item, "category"):
|
||||
return [models.Category(item.category).label]
|
||||
|
||||
|
||||
# TODO: newsletter editions feed
|
||||
class EditionFeed(Feed):
|
||||
"""Newsletter editions"""
|
||||
|
||||
feed_type = Atom1Feed
|
||||
link = "/newsletter-editions"
|
||||
feed_url = "/newsletter-editions/feed"
|
||||
feed_guid = f"https://{settings.DOMAIN}/newsletter-editions/feed"
|
||||
|
||||
title = "Aus GLAMR Blog newsletter editions"
|
||||
description = "Newsletters from Australasian Galleries, Libraries, Archives, Museums, Records and associated blogs"
|
||||
|
||||
def items(self):
|
||||
"""each article in the feed"""
|
||||
return models.Edition.objects.order_by("-pubdate")[:20]
|
||||
|
||||
def item_title(self, item):
|
||||
"""article title"""
|
||||
return item.title
|
||||
|
||||
def item_description(self, item):
|
||||
"""article description"""
|
||||
return item.description
|
||||
|
||||
def item_author_name(self, item):
|
||||
"""article author"""
|
||||
return item.author_name
|
||||
|
||||
def item_link(self, item):
|
||||
"""item url"""
|
||||
return item.url
|
||||
|
||||
def item_pubdate(self, item):
|
||||
"""article publication date"""
|
||||
return item.pubdate
|
||||
|
||||
def item_updateddate(self, item):
|
||||
"""updated date"""
|
||||
return item.updateddate
|
||||
|
||||
def item_categories(self, item):
|
||||
"""newsletter category"""
|
||||
return [models.Category(item.newsletter.category).label]
|
||||
|
||||
|
||||
class CombinedFeed(Feed):
|
||||
"""Combined Atom feed for everything"""
|
||||
|
||||
feed_type = Atom1Feed
|
||||
link = "/"
|
||||
feed_url = "/feed"
|
||||
feed_guid = f"https://{settings.DOMAIN}/feed"
|
||||
|
||||
title = "Aus GLAMR"
|
||||
description = "Latest news and opinion from Australasian Galleries, Libraries, Archives, Museums, Records professionals"
|
||||
categories = [
|
||||
"GLAM",
|
||||
"GLAMR",
|
||||
"Galleries",
|
||||
"Libraries",
|
||||
"Archives",
|
||||
"Museums",
|
||||
"Records",
|
||||
]
|
||||
feed_copyright = "Copyright is owned by individual authors"
|
||||
ttl = 600
|
||||
|
||||
def items(self):
|
||||
"""items for the feed"""
|
||||
|
||||
blog_objects = models.Blog.objects.filter(
|
||||
approved=True, suspended=False, active=True
|
||||
)
|
||||
|
||||
posts = models.Article.objects.all()
|
||||
|
||||
newsletters = models.Newsletter.objects.filter(approved=True, active=True)
|
||||
|
||||
editions = models.Edition.objects.all()
|
||||
|
||||
groups = models.Group.objects.filter(approved=True)
|
||||
|
||||
events = models.Event.objects.filter(approved=True)
|
||||
|
||||
cfps = models.CallForPapers.objects.all()
|
||||
|
||||
result_list = sorted(
|
||||
chain(blog_objects, posts, newsletters, editions, groups, events, cfps),
|
||||
key=attrgetter("pubdate"),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
return result_list[:30]
|
||||
|
||||
def item_title(self, item):
|
||||
"""title or name"""
|
||||
return item.name if hasattr(item, "name") else item.title
|
||||
|
||||
def item_description(self, item):
|
||||
"""description"""
|
||||
return (
|
||||
item.description
|
||||
if hasattr(item, "description")
|
||||
else item.details
|
||||
if hasattr(item, "details")
|
||||
else None
|
||||
)
|
||||
|
||||
def item_link(self, item):
|
||||
"""item url"""
|
||||
return item.url if hasattr(item, "url") else item.event.url
|
||||
|
||||
def item_guid(self, item):
|
||||
"""guid"""
|
||||
|
||||
return item.url if hasattr(item, "url") else f"{item.event.url}-cfp-{item.id}"
|
||||
|
||||
def item_author_name(self, item):
|
||||
"""author"""
|
||||
return getattr(item, "author_name", None)
|
||||
|
||||
def item_pubdate(self, item):
|
||||
"""date item was published"""
|
||||
|
||||
pubdate = getattr(item, "pubdate", None)
|
||||
return pubdate
|
||||
|
||||
def item_updateddate(self, item):
|
||||
"""updated date"""
|
||||
if hasattr(item, "updateddate"):
|
||||
return item.updateddate
|
||||
|
||||
if hasattr(item, "pubdate"):
|
||||
return item.pubdate
|
||||
|
||||
return None
|
||||
|
||||
def item_categories(self, item):
|
||||
"""GLAMR category or tags"""
|
||||
|
||||
if hasattr(item, "category"):
|
||||
return [models.Category(item.category).label]
|
||||
|
||||
if hasattr(item, "tags"):
|
||||
categories = []
|
||||
for tag in item.tags.all():
|
||||
categories.append(tag.name)
|
||||
return categories
|
||||
|
||||
return None
|
||||
|
|
|
@ -51,9 +51,10 @@ class Blogs(View):
|
|||
|
||||
for blog in blogs:
|
||||
blog.category_name = models.Category(blog.category).label
|
||||
data = {"title": "Blogs and websites", "blogs": blogs}
|
||||
data = {"title": "Blogs and websites", "blogs": blogs, "category": category}
|
||||
return render(request, "browse/blogs.html", data)
|
||||
|
||||
|
||||
class Articles(View):
|
||||
"""Blog articles"""
|
||||
|
||||
|
@ -65,8 +66,7 @@ class Articles(View):
|
|||
return render(request, "browse/articles.html", data)
|
||||
|
||||
|
||||
|
||||
class Conferences(View):
|
||||
class Events(View):
|
||||
"""browse the list of conferences"""
|
||||
|
||||
def get(self, request, category=None):
|
||||
|
@ -90,7 +90,7 @@ class Conferences(View):
|
|||
.last()
|
||||
)
|
||||
|
||||
data = {"title": "Upcoming events", "cons": cons}
|
||||
data = {"title": "Upcoming events", "cons": cons, "category": category}
|
||||
return render(request, "browse/events.html", data)
|
||||
|
||||
|
||||
|
@ -124,19 +124,29 @@ class Groups(View):
|
|||
for group in groups:
|
||||
group.category_name = models.Category(group.category).label
|
||||
group.reg_type = models.utils.GroupType(group.type).label
|
||||
data = {"title": "Groups and discussion lists", "groups": groups}
|
||||
data = {
|
||||
"title": "Groups and discussion lists",
|
||||
"groups": groups,
|
||||
"category": category,
|
||||
}
|
||||
return render(request, "browse/groups.html", data)
|
||||
|
||||
|
||||
class Newsletters(View):
|
||||
"""browse the list of groups"""
|
||||
|
||||
def get(self, request):
|
||||
def get(self, request, category=None):
|
||||
"""here they are"""
|
||||
if category:
|
||||
news = models.Newsletter.objects.filter(
|
||||
approved=True, category=category
|
||||
).order_by("name")
|
||||
else:
|
||||
news = models.Newsletter.objects.filter(approved=True).order_by("name")
|
||||
|
||||
for letter in news:
|
||||
letter.category_name = models.Category(letter.category).label
|
||||
data = {"title": "Newsletters", "news": news}
|
||||
data = {"title": "Newsletters", "news": news, "category": category}
|
||||
return render(request, "browse/newsletters.html", data)
|
||||
|
||||
|
||||
|
@ -423,7 +433,7 @@ class Search(View):
|
|||
|
||||
|
||||
class Browse(View):
|
||||
"""browse by clicking on a tag"""
|
||||
"""browse blog articles by clicking on a tag"""
|
||||
|
||||
def get(self, request):
|
||||
"""display browse results"""
|
||||
|
|
|
@ -11,7 +11,7 @@ services:
|
|||
web:
|
||||
build: .
|
||||
env_file: .env
|
||||
command: python manage.py runserver 0.0.0.0:8000
|
||||
command: python manage.py runserver 0.0.0.0:8000 # only use in dev
|
||||
# command: gunicorn --env DJANGO_SETTINGS_MODULE=ausglamr.settings ausglamr.wsgi --workers=10 --threads=4 -b 0.0.0.0:8000
|
||||
volumes:
|
||||
- .:/app
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Thanks Mouse Reeve for letting me steal your idea.
|
||||
# Thanks Mouse Reeve for letting me steal their idea.
|
||||
|
||||
# exit on errors
|
||||
set -e
|
||||
|
@ -31,9 +31,9 @@ case "$CMD" in
|
|||
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
|
||||
${DOCKER_PATH} exec -u root ausglamr_db_1 pg_dump -v -Fc -U ausglamr -d "ausglamr" -f /tmp/ausglamr_backup.dump
|
||||
${DOCKER_PATH} cp ausglamr_db_1:/tmp/ausglamr_backup.dump ${BACKUPS_DIR}/
|
||||
mv ${BACKUPS_DIR}/ausglamr_backup.dump ${BACKUPS_DIR}/ausglamr_backup_$(date +'%a').dump
|
||||
;;
|
||||
black)
|
||||
docker compose run --rm web black ausglamr blogs
|
||||
|
|
14
robots.txt
Normal file
14
robots.txt
Normal file
|
@ -0,0 +1,14 @@
|
|||
User-agent: AdsBot-Google
|
||||
Disallow: /
|
||||
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: ChatGPT-User
|
||||
Disallow: /
|
||||
|
||||
User-agent: Google-Extended
|
||||
Disallow: /
|
||||
|
||||
User-agent: CCBot
|
||||
Disallow: /
|
Loading…
Reference in a new issue