Skip to content

Commit 1cdeed4

Browse files
committed
Add support for bluesky and mastodon, drop support for twitter
This also makes the framework more generic for adding more options in the future. And make the poster a file that can easily be copied between projects as we will need it elsewhere as well. In passing, add the #postgresql hashtag to the posts
1 parent 2f7b16e commit 1cdeed4

9 files changed

Lines changed: 313 additions & 98 deletions

File tree

pgweb/news/forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def clean(self):
5454

5555
class Meta:
5656
model = NewsArticle
57-
exclude = ('date', 'submitter', 'modstate', 'tweeted', 'firstmoderator')
57+
exclude = ('date', 'submitter', 'modstate', 'postedto', 'firstmoderator')
5858
widgets = {
5959
'tags': forms.CheckboxSelectMultiple,
6060
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Script to post previously unposted news to social media providers
4+
#
5+
#
6+
7+
from django.core.management.base import BaseCommand, CommandError
8+
from django.db import connection
9+
from django.template.defaultfilters import slugify
10+
from django.conf import settings
11+
12+
from datetime import datetime, timedelta
13+
import time
14+
15+
from pgweb.util.moderation import ModerationState
16+
from pgweb.news.models import NewsArticle
17+
from pgweb.util.socialposter import get_all_providers
18+
19+
20+
allproviders, allprovidernames = get_all_providers(settings)
21+
22+
23+
class Command(BaseCommand):
24+
help = 'Post to social media'
25+
26+
def handle(self, *args, **options):
27+
curs = connection.cursor()
28+
curs.execute("SELECT pg_try_advisory_lock(62387372)")
29+
if not curs.fetchall()[0][0]:
30+
raise CommandError("Failed to get advisory lock, existing social_post process stuck?")
31+
32+
articles = list(NewsArticle.objects.filter(modstate=ModerationState.APPROVED, date__gt=datetime.now() - timedelta(days=7)).exclude(postedto__contains=allprovidernames).order_by('date'))
33+
if not len(articles):
34+
return
35+
36+
for i, a in enumerate(articles):
37+
if i != 0:
38+
# Don't post more often than once / 30 seconds, to not trigger flooding.
39+
time.sleep(30)
40+
41+
statusstr = "News: {0}\n\n{1}/about/news/{2}-{3}/\n\n#postgresql".format(a.title[:140 - 40], settings.SITE_ROOT, slugify(a.title), a.id)
42+
43+
for p in allproviders:
44+
if p.name not in a.postedto:
45+
postid = p.post(statusstr)
46+
if postid is not None:
47+
a.postedto[p.name] = postid
48+
a.save(update_fields=['postedto'])
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Script to register with social providers
4+
#
5+
#
6+
7+
from django.core.management.base import BaseCommand
8+
from django.conf import settings
9+
10+
from pgweb.util.socialposter import get_all_providers
11+
12+
allproviders, allprovidernames = get_all_providers(settings, True)
13+
14+
15+
class Command(BaseCommand):
16+
help = 'Register with social providers'
17+
18+
def add_arguments(self, parser):
19+
parser.add_argument('provider', choices=allprovidernames)
20+
21+
def handle(self, *args, **options):
22+
for p in allproviders:
23+
if p.name == options['provider']:
24+
r = p.register('pgweb')
25+
if r:
26+
print(r)
27+
else:
28+
print("{} already registered.".format(p.name))
29+
break
30+
else:
31+
print("Provider {} not found.".format(p.name))

pgweb/news/management/commands/twitter_post.py

Lines changed: 0 additions & 52 deletions
This file was deleted.

pgweb/news/management/commands/twitter_register.py

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generated by Django 5.2.10 on 2026-04-02 09:09
2+
3+
import django.contrib.postgres.fields
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('news', '0007_news_date_idx'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='newsarticle',
16+
name='postedto',
17+
field=models.JSONField(null=False, blank=True, default=dict)
18+
),
19+
migrations.RunSQL(
20+
"UPDATE news_newsarticle SET postedto=jsonb_build_object('twitter', true) WHERE tweeted",
21+
"UPDATE news_newsarticle SET tweeted = true WHERE postedto ? 'twitter'",
22+
),
23+
migrations.RemoveField(
24+
model_name='newsarticle',
25+
name='tweeted',
26+
),
27+
]

pgweb/news/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class NewsArticle(TwoModeratorsMixin, TristateModerateModel):
3232
date = models.DateField(null=False, blank=False, default=date.today, db_index=True)
3333
title = models.CharField(max_length=200, null=False, blank=False)
3434
content = models.TextField(null=False, blank=False)
35-
tweeted = models.BooleanField(null=False, blank=False, default=False)
35+
postedto = models.JSONField(null=False, blank=True, default=dict)
3636
tags = models.ManyToManyField(NewsTag, blank=False, help_text="Select the tags appropriate for this post")
3737

3838
account_edit_suburl = 'news'

pgweb/settings.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,17 @@
184184
OAUTH = {} # OAuth providers and keys
185185
PGDG_ORG_ID = -1 # id of the PGDG organisation entry
186186

187+
188+
# Mastodon configuriation is done using manage.py social_register
189+
MASTODON_BASEURL = None
190+
MASTODON_CLIENTID = None
191+
MASTODON_CLIENTSECRET = None
192+
MASTODON_TOKEN = None
193+
# Bluesky is configured in settings_local.py
194+
BLUESKY_USER = None
195+
BLUESKY_PASSWORD = None # NOTE! This should be an "app password", not the main password!
196+
197+
187198
# For debug toolbar, can then be fully configured in settings_local.py
188199
DEBUG_TOOLBAR = False
189200
INTERNAL_IPS = [

0 commit comments

Comments
 (0)