From 5984aed4c2c0e717aab3ecedf55b0e029c158d93 Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Tue, 9 Oct 2018 22:18:39 +0200 Subject: [PATCH] Support auto-tweeting when sponsorship benefit is confirmed This allows automatic posting of tweets like "welcome xyz as foobar sponsor awesomeconf". Which will make it less likely to forget... --- docs/confreg/sponsors.md | 5 ++++ postgresqleu/confreg/jinjafunc.py | 23 +++++++++++++++ postgresqleu/confsponsor/backendforms.py | 28 ++++++++++++++++++- .../migrations/0010_benefit_tweet_template.py | 20 +++++++++++++ postgresqleu/confsponsor/models.py | 1 + postgresqleu/confsponsor/views.py | 12 ++++++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 postgresqleu/confsponsor/migrations/0010_benefit_tweet_template.py diff --git a/docs/confreg/sponsors.md b/docs/confreg/sponsors.md index d78d19e..b458c41 100644 --- a/docs/confreg/sponsors.md +++ b/docs/confreg/sponsors.md @@ -179,6 +179,11 @@ Class parameters JSON format. Will be automatically populated with a default set of parameters when created, but their values have to be set. +Tweet template +: A template, in jinja2 format, used to generate tweets when this +benefit is confirmed. If left empty, no tweet is posted. Can reference +*sponsor*, *level*, *conference* and *benefit* variables that will be +filled with information about the current conference. ### Sponsorship contract diff --git a/postgresqleu/confreg/jinjafunc.py b/postgresqleu/confreg/jinjafunc.py index 0a0aa15..b14bc17 100644 --- a/postgresqleu/confreg/jinjafunc.py +++ b/postgresqleu/confreg/jinjafunc.py @@ -1,6 +1,7 @@ from django.http import Http404, HttpResponse from django.template.backends.utils import csrf_input_lazy, csrf_token_lazy from django.template import defaultfilters +from django.core.exceptions import ValidationError from django.contrib.messages.api import get_messages from django.utils.text import slugify from django.conf import settings @@ -266,3 +267,25 @@ def render_jinja_conference_response(request, conference, pagemagic, templatenam c.update(settings_context_unicode()) return HttpResponse(t.render(**c), content_type='text/html') + + + + + +# Small sandboxed jinja templates that can be configured in system +def render_sandboxed_template(templatestr, context): + env = ConfSandbox(loader=jinja2.DictLoader({'t': templatestr})) + t = env.get_template('t') + return t.render(context) + +class JinjaTemplateValidator(object): + def __init__(self, context={}): + self.context = context + + def __call__(self, s): + try: + render_sandboxed_template(s, self.context) + except jinja2.TemplateSyntaxError, e: + raise ValidationError("Template syntax error: %s" % e) + except Exception, e: + raise ValidationError("Failed to parse template: %s" % e) diff --git a/postgresqleu/confsponsor/backendforms.py b/postgresqleu/confsponsor/backendforms.py index 9a3dddc..0f1a1f8 100644 --- a/postgresqleu/confsponsor/backendforms.py +++ b/postgresqleu/confsponsor/backendforms.py @@ -6,6 +6,7 @@ from postgresqleu.util.magic import magicdb from postgresqleu.util.widgets import RequiredFileUploadWidget, PrettyPrintJsonWidget from postgresqleu.confreg.backendforms import BackendForm from postgresqleu.confreg.backendlookups import GeneralAccountLookup +from postgresqleu.confreg.jinjafunc import JinjaTemplateValidator, render_sandboxed_template from models import Sponsor from models import SponsorshipLevel, SponsorshipContract, SponsorshipBenefit @@ -39,14 +40,39 @@ class BackendSponsorshipLevelBenefitForm(BackendForm): helplink='sponsors#benefit' json_fields = ['class_parameters', ] markdown_fields = ['benefitdescription', 'claimprompt', ] + dynamic_preview_fields = ['tweet_template'] + class Meta: model = SponsorshipBenefit fields = ['benefitname', 'benefitdescription', 'sortkey', 'benefit_class', - 'claimprompt', 'class_parameters', ] + 'claimprompt', 'class_parameters', 'tweet_template'] widgets = { 'class_parameters': PrettyPrintJsonWidget, } + def fix_fields(self): + self.fields['tweet_template'].validators = [ + JinjaTemplateValidator({ + 'conference': self.conference, + 'benefit': self.instance, + 'level': self.instance.level, + 'sponsor': Sponsor(name='Test'), + }), + ] + + @classmethod + def get_dynamic_preview(self, fieldname, s, objid): + if fieldname == 'tweet_template': + if objid: + o = self.Meta.model.objects.get(pk=objid) + return render_sandboxed_template(s, { + 'benefit': o, + 'level': o.level, + 'conference': o.level.conference, + 'sponsor': Sponsor(name='Test'), + }) + return '' + def clean(self): cleaned_data = super(BackendSponsorshipLevelBenefitForm, self).clean() if cleaned_data.get('benefit_class') >= 0: diff --git a/postgresqleu/confsponsor/migrations/0010_benefit_tweet_template.py b/postgresqleu/confsponsor/migrations/0010_benefit_tweet_template.py new file mode 100644 index 0000000..8eca775 --- /dev/null +++ b/postgresqleu/confsponsor/migrations/0010_benefit_tweet_template.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.10 on 2018-10-09 21:13 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('confsponsor', '0009_vat_allow_null'), + ] + + operations = [ + migrations.AddField( + model_name='sponsorshipbenefit', + name='tweet_template', + field=models.TextField(blank=True), + ), + ] diff --git a/postgresqleu/confsponsor/models.py b/postgresqleu/confsponsor/models.py index 51c3ce1..c27ba86 100644 --- a/postgresqleu/confsponsor/models.py +++ b/postgresqleu/confsponsor/models.py @@ -67,6 +67,7 @@ class SponsorshipBenefit(models.Model): claimprompt = models.TextField(null=False, blank=True) benefit_class = models.IntegerField(null=True, blank=True, default=None, choices=benefit_choices) class_parameters = JSONField(blank=True, null=False) + tweet_template = models.TextField(null=False, blank=True) def __unicode__(self): return self.benefitname diff --git a/postgresqleu/confsponsor/views.py b/postgresqleu/confsponsor/views.py index fd4a5a8..ef40739 100644 --- a/postgresqleu/confsponsor/views.py +++ b/postgresqleu/confsponsor/views.py @@ -11,6 +11,8 @@ from datetime import datetime, timedelta from postgresqleu.auth import user_search, user_import from postgresqleu.confreg.models import Conference, PrepaidVoucher, DiscountCode +from postgresqleu.confreg.models import ConferenceTweetQueue +from postgresqleu.confreg.jinjafunc import render_sandboxed_template from postgresqleu.mailqueue.util import send_simple_mail from postgresqleu.util.storage import InlineEncodedStorage from postgresqleu.util.decorators import superuser_required @@ -483,6 +485,16 @@ def _confirm_benefit(request, benefit): sendername=conference.conferencename, ) + # Potentially send tweet + if benefit.benefit.tweet_template: + ConferenceTweetQueue(conference=conference, datetime=datetime.now(), + contents=render_sandboxed_template(benefit.benefit.tweet_template, { + 'benefit': benefit.benefit, + 'level': benefit.benefit.level, + 'conference': conference, + 'sponsor': benefit.sponsor + })).save() + @login_required def sponsor_admin_sponsor(request, confurlname, sponsorid): if request.user.is_superuser: -- 2.39.5