Skip to content

Commit

Permalink
Add a new BlogPost model for displaying content on the homepage and i…
Browse files Browse the repository at this point in the history
…n email updates

Using markdownx to make a nice Markdown editor in the admin.
  • Loading branch information
JoshData committed Jan 25, 2024
1 parent 077ddb3 commit 031ea63
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 74 deletions.
73 changes: 28 additions & 45 deletions events/management/commands/send_email_updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
utm = "utm_campaign=govtrack_email_update&utm_source=govtrack/email_update&utm_medium=email"
template_body_text = None
template_body_html = None
announce = None
latest_blog_post = None

class Command(BaseCommand):
help = 'Sends out email updates of events to subscribing users.'
Expand All @@ -49,7 +49,7 @@ def add_arguments(self, parser):
def handle(self, *args, **options):
global template_body_text
global template_body_html
global announce
global latest_blog_post

if options["mode"][0] not in ('daily', 'weekly', 'testadmin', 'testcount'):
print("Specify daily or weekly or testadmin or testcount.")
Expand Down Expand Up @@ -109,7 +109,7 @@ def handle(self, *args, **options):
# load globals
template_body_text = get_template("events/emailupdate_body.txt")
template_body_html = get_template("events/emailupdate_body.html")
announce = load_announcement("website/email/email_update_announcement.md", options["mode"][0] == "testadmin")
latest_blog_post = load_latest_blog_post()

# counters for analytics on what we sent
counts = {
Expand Down Expand Up @@ -235,10 +235,12 @@ def pool_worker(conn):

def send_email_update(user_id, list_email_freq, send_mail, mark_lists, send_old_events, mail_connection):
global launch_time
global latest_blog_post

user_start_time = datetime.now()

user = User.objects.get(id=user_id)
profile = UserProfile.objects.get(user=user)

# get the email's From: header and return path
emailfromaddr = getattr(settings, 'EMAIL_UPDATES_FROMADDR',
Expand Down Expand Up @@ -272,10 +274,15 @@ def send_email_update(user_id, list_email_freq, send_mail, mark_lists, send_old_
if most_recent_event is None: most_recent_event = max_id
most_recent_event = max(most_recent_event, max_id)

# Suppress the latest blog post if the user was already sent it.
if latest_blog_post and profile.last_blog_post_emailed >= latest_blog_post.id:
latest_blog_post = None

user_querying_end_time = datetime.now()

# Don't send an empty email.... unless we're testing and we want to send some old events.
if len(eventslists) == 0 and not send_old_events and announce is None:
# Don't send an empty email (no events and no latest blog post)
# .... unless we're testing and we want to send some old events.
if len(eventslists) == 0 and not send_old_events and latest_blog_post is None:
return {
"total_time_querying": user_querying_end_time-user_start_time,
}
Expand Down Expand Up @@ -321,15 +328,15 @@ def send_email_update(user_id, list_email_freq, send_mail, mark_lists, send_old_
"emailpingurl": emailpingurl,
"body_text": body_text,
"body_html": body_html,
"announcement": announce,
"latest_blog_post": latest_blog_post,
"SITE_ROOT_URL": settings.SITE_ROOT_URL,
"utm": utm,
},
headers={
'Reply-To': emailfromaddr,
'Auto-Submitted': 'auto-generated',
'X-Auto-Response-Suppress': 'OOF',
'X-Unsubscribe-Link': UserProfile.objects.get(user=user).get_one_click_unsub_url(),
'X-Unsubscribe-Link': profile.get_one_click_unsub_url(),
},
fail_silently=False,
connection=mail_connection,
Expand Down Expand Up @@ -358,6 +365,9 @@ def send_email_update(user_id, list_email_freq, send_mail, mark_lists, send_old_
sublist.last_event_mailed = max(sublist.last_event_mailed, most_recent_event) if sublist.last_event_mailed is not None else most_recent_event
sublist.last_email_sent = launch_time
sublist.save()
if latest_blog_post:
profile.last_blog_post_emailed = latest_blog_post.id
profile.save()

user_sending_end_time = datetime.now()

Expand All @@ -369,46 +379,19 @@ def send_email_update(user_id, list_email_freq, send_mail, mark_lists, send_old_
"total_time_sending": user_sending_end_time-user_rendering_end_time,
}

def load_announcement(template_path, testing):
# Load the Markdown template for the current blast.
templ = get_template(template_path)

# Get the text-only body content, which also includes some email metadata.
# Replace Markdown-style [text][href] links with the text plus bracketed href.
ctx = { "format": "text", "utm": "" }
body_text = templ.render(ctx).strip()
body_text = re.sub(r"\[(.*?)\]\((.*?)\)", r"\1 at \2", body_text)

# The top of the text content contains metadata in YAML format,
# with "id" and "subject" required and active: true or rundate set to today's date in ISO format.
meta_info, body_text = body_text.split("----------", 1)
body_text = body_text.strip()
meta_info = yaml.load(meta_info)

# Under what cases do we use this announcement?
if meta_info.get("active"):
pass # active is set to something truthy
elif meta_info.get("rundate") == launch_time.date().isoformat():
pass # rundate matches date this job was started
elif "rundate" in meta_info and testing:
pass # when testing ignore the actual date set
else:
# the announcement file is inactive/stale
def load_latest_blog_post():
from website.models import BlogPost
latest_blog_post = BlogPost.objects\
.filter(published=True)\
.order_by('-created')\
.first()
if not latest_blog_post:
return None

# Get the HTML body content.
ctx = {
"format": "html",
"utm": "",
}
body_html = templ.render(ctx).strip()
body_html = markdown(body_html)
# Pre-render the HTML and plain text versions.
latest_blog_post.body_html = latest_blog_post.body_html()
latest_blog_post.body_text = latest_blog_post.body_text()

# Store everything in meta_info.

meta_info["body_text"] = body_text
meta_info["body_html"] = body_html

return meta_info
return latest_blog_post


1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ cmarkgfm<0.5.0
django_otp
Mastodon.py
requests
django-markdownx
4 changes: 4 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
'crispy_forms',
'django_otp',
'django_otp.plugins.otp_totp',
#'django_otp.plugins.otp_static', # necessary for bootstrapping access to the admin
'markdownx',

'haystack',
'htmlemailer',
Expand Down Expand Up @@ -208,3 +210,5 @@
SHOW_TOOLBAR_CALLBACK = lambda : True

OTP_TOTP_ISSUER = "GovTrack.us"

MARKDOWNX_MARKDOWNIFY_FUNCTION = 'website.templatetags.govtrack_utils.markdown'
1 change: 1 addition & 0 deletions static/markdownx
18 changes: 8 additions & 10 deletions templates/events/emailupdate.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,23 +92,21 @@
<tr>
<td>

<!-- ANNOUNCEMENT -->
{% if announcement %}
<div style="background-color:#ffffff;border-radius:6px;text-align:left !important;">
<div id="announcement-holder">
{% if announcement.subject %}<h2>{{announcement.subject}}</h2>{% endif %}
{{announcement.body_html|safe}}
</div>
</div>
{% endif %}

<div class="section">
Like these updates?
A <a href="https://www.patreon.com/govtrack">recurring tip</a> or <a href="https://www.govtrack.us/accounts/membership">one-time tip</a> will help us keep this service free for everyone.
Join us on <a href="https://mastodon.social/@GovTrack" title="Mastodon Page">Mastodon</a>
for more updates.
</div>

{% if latest_blog_post %}
<div style="background-color:#ffffff;border-radius:6px;text-align:left !important;">
<div id="announcement-holder">
<h2>{{latest_blog_post.title}}</h2>
{{latest_blog_post.body_html|safe}}
</div>
</div>
{% endif %}

<div class="section">
{{body_html|safe}}
Expand Down
11 changes: 9 additions & 2 deletions templates/events/emailupdate.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
{% autoescape off %}GovTrack Email Update
=====================

{% if announcement %}----- {{announcement.body_text}} -----
This is your email update from www.GovTrack.us. To change your email update settings, including to unsubscribe, go to {{SITE_ROOT_URL}}/accounts/profile.

{% endif %}This is your email update from www.GovTrack.us. To change your email update settings, including to unsubscribe, go to {{SITE_ROOT_URL}}/accounts/profile.
{% if latest_blog_post %}=====================================================================

{{latest_blog_post.title}}

{{last_blog_post_emailed.body_text}}

{% endif %}
=====================================================================

{{body_text}}

Expand Down
2 changes: 1 addition & 1 deletion templates/events/emailupdate_subject.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
GovTrack Update for {{date}}{% if announcement.subject %} | {{announcement.subject}}{% endif %}
Activity in Congress{% if latest_blog_post %}: {{latest_blog_post.title}}{% endif %} ({{date}})
13 changes: 7 additions & 6 deletions templates/website/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,20 @@ <h2>Find legislation that affects you:</h2>

<div class="row">

<div class="col-md-5">
<div class="col-lg-6">
{% if latest_blog_post %}
<div class="card">
<div class="card-header">How a bill actually becomes a law... in 2020</div>
<div class="card-header">{{latest_blog_post.title}}</div>
<div class="card-body">
<p>Middle school social studies textbooks and Schoolhouse Rock songs paint a stereotypical portrait of how congressional legislation gets passed. But in real life, the story of how federal laws actually get enacted usually proves far more complex. To examine how, GovTrack Insider browsed through all the laws enacted by Congress in 2019–20, looking for one that seemed both particularly interesting and undercovered by national media.</p>
<a href="https://govtrackinsider.com/how-a-bill-actually-becomes-a-law-in-2020-the-emancipation-national-historic-trail-study-act-768a301974ff">Read
the story of The Emancipation National Historic Trail Study Act &raquo;</a>
{{latest_blog_post.body_html|safe}}
<div style="font-style: italic">&mdash; {{latest_blog_post.created|date:"SHORT_DATETIME_FORMAT"}}</div>
</div>
</div>
{% endif %}

</div>

<div class="col-md-7">
<div class="col-lg-6">

{% for post_group in post_groups %}
<div class="card" style="margin-bottom: 1.5em">
Expand Down
5 changes: 3 additions & 2 deletions urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@

urlpatterns += [
url(r'^admin/', admin.site.urls),
url('markdownx/', include('markdownx.urls')),

# main URLs
# main URLs
url(r'', include('redirect.urls')),
url(r'', include('website.urls')),
url(r'^congress/members(?:$|/)', include('person.urls')),
Expand All @@ -44,7 +45,7 @@
url(r'^accounts/logout$', auth_views.LogoutView.as_view(), { "redirect_field_name": "next" }),
url(r'^accounts/profile$', registration.views.profile, name='registration.views.profile'),

url(r'^dump_request', website.views.dumprequest),
url(r'^dump_request', website.views.dumprequest),
]

# sitemaps
Expand Down
6 changes: 5 additions & 1 deletion website/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8

from django.contrib import admin
from markdownx.admin import MarkdownxModelAdmin
from website.models import *

class UserProfileAdmin(admin.ModelAdmin):
Expand All @@ -11,9 +12,12 @@ class UserProfileAdmin(admin.ModelAdmin):
class MediumPostAdmin(admin.ModelAdmin):
list_display = ['published', 'title', 'url']

class BlogPostAdmin(MarkdownxModelAdmin):
list_display = ['title', 'published', 'created', 'updated']

admin.site.register(UserProfile, UserProfileAdmin)
admin.site.register(MediumPost, MediumPostAdmin)
admin.site.register(Community)
admin.site.register(CommunityMessageBoard)
admin.site.register(CommunityMessage)

admin.site.register(BlogPost, BlogPostAdmin)
34 changes: 34 additions & 0 deletions website/migrations/0008_blogpost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 2.2.28 on 2024-01-12 16:11

from django.db import migrations, models
import markdownx.models


class Migration(migrations.Migration):

dependencies = [
('website', '0007_ipaddrinfo'),
]

operations = [
migrations.CreateModel(
name='BlogPost',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=128)),
('body', markdownx.models.MarkdownxField()),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('published', models.BooleanField(db_index=True, default=False)),
],
options={
'index_together': {('published', 'created')},
},
),

migrations.AddField(
model_name='userprofile',
name='last_blog_post_emailed',
field=models.IntegerField(default=0),
),
]
26 changes: 26 additions & 0 deletions website/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.conf import settings

from jsonfield import JSONField
from markdownx.models import MarkdownxField

from events.models import Feed, SubscriptionList

Expand All @@ -11,6 +12,7 @@ class UserProfile(models.Model):
massemail = models.BooleanField(default=True) # may we send you mail?
old_id = models.IntegerField(blank=True, null=True) # from the pre-2012 GovTrack database
last_mass_email = models.IntegerField(default=0)
last_blog_post_emailed = models.IntegerField(default=0)
congressionaldistrict = models.CharField(max_length=4, blank=True, null=True, db_index=True) # or 'XX00' if the user doesn't want to provide it

# monetization
Expand Down Expand Up @@ -436,3 +438,27 @@ class IpAddrInfo(models.Model):
last_hit = models.DateTimeField(auto_now=True, db_index=True)
hits = models.IntegerField(default=1, db_index=True)
leadfeeder = JSONField(default={}, blank=True, null=True)

class BlogPost(models.Model):
title = models.CharField(max_length=128)
body = MarkdownxField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=False, db_index=True)

class Meta:
index_together = [("published", "created")]

def __str__(self):
return self.title

def body_html(self):
from website.templatetags.govtrack_utils import markdown
return markdown(self.body)

def body_text(self):
# Replace Markdown-style [text][href] links with the text plus bracketed href.
import re
body_text = self.body
body_text = re.sub(r"\[(.*?)\]\((.*?)\)", r"\1 at \2", body_text)
return body_text
Loading

0 comments on commit 031ea63

Please sign in to comment.