Send welcome email to new Cloud subscribers
This commit is contained in:
parent
0b34c5c1c6
commit
c2518e9ae1
@ -85,10 +85,12 @@ class CloudExtension(PillarExtension):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setup_app(self, app):
|
def setup_app(self, app):
|
||||||
from . import routes, webhooks
|
from . import routes, webhooks, eve_hooks, email
|
||||||
|
|
||||||
routes.setup_app(app)
|
routes.setup_app(app)
|
||||||
app.register_api_blueprint(webhooks.blueprint, '/webhooks')
|
app.register_api_blueprint(webhooks.blueprint, '/webhooks')
|
||||||
|
eve_hooks.setup_app(app)
|
||||||
|
email.setup_app(app)
|
||||||
|
|
||||||
|
|
||||||
def _get_current_cloud():
|
def _get_current_cloud():
|
||||||
|
33
cloud/email.py
Normal file
33
cloud/email.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import flask
|
||||||
|
|
||||||
|
from pillar.auth import UserClass
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def queue_welcome_mail(user: UserClass):
|
||||||
|
"""Queue a welcome email for execution by Celery."""
|
||||||
|
assert user.email
|
||||||
|
log.info('queueing welcome email to %s', user.email)
|
||||||
|
|
||||||
|
subject = 'Welcome to Blender Cloud'
|
||||||
|
render = functools.partial(flask.render_template, subject=subject, user=user)
|
||||||
|
text = render('emails/welcome.txt')
|
||||||
|
html = render('emails/welcome.html')
|
||||||
|
|
||||||
|
from pillar.celery import email_tasks
|
||||||
|
email_tasks.send_email.delay(user.full_name, user.email, subject, text, html)
|
||||||
|
|
||||||
|
|
||||||
|
def user_subscription_changed(user: UserClass, *, grant_roles: set, revoke_roles: set):
|
||||||
|
if user.has_cap('subscriber') and 'has_subscription' in grant_roles:
|
||||||
|
log.info('user %s just got a new subscription', user.email)
|
||||||
|
queue_welcome_mail(user)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_app(app):
|
||||||
|
from pillar.api.blender_cloud import subscription
|
||||||
|
subscription.user_subscription_updated.connect(user_subscription_changed)
|
39
cloud/eve_hooks.py
Normal file
39
cloud/eve_hooks.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from pillar.auth import UserClass
|
||||||
|
|
||||||
|
from . import email
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def welcome_new_user(user_doc: dict):
|
||||||
|
"""Sends a welcome email to a new user."""
|
||||||
|
|
||||||
|
user_email = user_doc.get('email')
|
||||||
|
if not user_email:
|
||||||
|
log.warning('user %s has no email address', user_doc.get('_id', '-no-id-'))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Only send mail to new users when they actually are subscribers.
|
||||||
|
user = UserClass.construct('', user_doc)
|
||||||
|
if not (user.has_cap('subscriber') or user.has_cap('can-renew-subscription')):
|
||||||
|
log.debug('user %s is new, but not a subscriber, so no email for them.', user_email)
|
||||||
|
return
|
||||||
|
|
||||||
|
email.queue_welcome_mail(user)
|
||||||
|
|
||||||
|
|
||||||
|
def welcome_new_users(user_docs: typing.List[dict]):
|
||||||
|
"""Sends a welcome email to new users."""
|
||||||
|
|
||||||
|
for user_doc in user_docs:
|
||||||
|
try:
|
||||||
|
welcome_new_user(user_doc)
|
||||||
|
except Exception:
|
||||||
|
log.exception('error sending welcome mail to user %s', user_doc)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_app(app):
|
||||||
|
app.on_inserted_users += welcome_new_users
|
@ -1,10 +1,10 @@
|
|||||||
import functools
|
import functools
|
||||||
import itertools
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
import flask
|
||||||
from flask import Blueprint, render_template, redirect, session, url_for, abort, flash
|
from flask import Blueprint, render_template, redirect, session, url_for, abort, flash
|
||||||
from pillarsdk import Node, Project, User, exceptions as sdk_exceptions, Group
|
from pillarsdk import Node, Project, User, exceptions as sdk_exceptions, Group
|
||||||
from pillarsdk.exceptions import ResourceNotFound
|
from pillarsdk.exceptions import ResourceNotFound
|
||||||
@ -372,6 +372,31 @@ def privacy():
|
|||||||
return render_template('privacy.html')
|
return render_template('privacy.html')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/emails/welcome.send')
|
||||||
|
@login_required
|
||||||
|
def emails_welcome_send():
|
||||||
|
from cloud import email
|
||||||
|
email.queue_welcome_mail(current_user)
|
||||||
|
return f'queued mail to {current_user.email}'
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/emails/welcome.html')
|
||||||
|
@login_required
|
||||||
|
def emails_welcome_html():
|
||||||
|
return render_template('emails/welcome.html',
|
||||||
|
subject='Welcome to Blender Cloud',
|
||||||
|
user=current_user)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/emails/welcome.txt')
|
||||||
|
@login_required
|
||||||
|
def emails_welcome_txt():
|
||||||
|
txt = render_template('emails/welcome.txt',
|
||||||
|
subject='Welcome to Blender Cloud',
|
||||||
|
user=current_user)
|
||||||
|
return flask.Response(txt, content_type='text/plain; charset=utf-8')
|
||||||
|
|
||||||
|
|
||||||
def setup_app(app):
|
def setup_app(app):
|
||||||
global _homepage_context
|
global _homepage_context
|
||||||
cached = app.cache.cached(timeout=300)
|
cached = app.cache.cached(timeout=300)
|
||||||
|
@ -126,11 +126,14 @@ def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]:
|
|||||||
provider='blender-id',
|
provider='blender-id',
|
||||||
full_name=wh_payload['full_name'])
|
full_name=wh_payload['full_name'])
|
||||||
|
|
||||||
user_doc['roles'] = [subscription.ROLES_BID_TO_PILLAR[r]
|
# Figure out the user's eventual roles. These aren't stored in the document yet,
|
||||||
for r in wh_payload.get('roles', [])
|
# because that's handled by the badger service.
|
||||||
if r in subscription.ROLES_BID_TO_PILLAR]
|
eventual_roles = [subscription.ROLES_BID_TO_PILLAR[r]
|
||||||
|
for r in wh_payload.get('roles', [])
|
||||||
|
if r in subscription.ROLES_BID_TO_PILLAR]
|
||||||
user_ob = UserClass.construct('', user_doc)
|
user_ob = UserClass.construct('', user_doc)
|
||||||
|
user_ob.roles = eventual_roles
|
||||||
|
user_ob.collect_capabilities()
|
||||||
create = (user_ob.has_cap('subscriber') or
|
create = (user_ob.has_cap('subscriber') or
|
||||||
user_ob.has_cap('can-renew-subscription') or
|
user_ob.has_cap('can-renew-subscription') or
|
||||||
current_app.org_manager.user_is_unknown_member(email))
|
current_app.org_manager.user_is_unknown_member(email))
|
||||||
@ -179,19 +182,23 @@ def user_modified():
|
|||||||
return '', 204
|
return '', 204
|
||||||
|
|
||||||
# Use direct database updates to change the email and full name.
|
# Use direct database updates to change the email and full name.
|
||||||
|
# Also updates the db_user dict so that local_user below will have
|
||||||
|
# the updated information.
|
||||||
updates = {}
|
updates = {}
|
||||||
if db_user['email'] != payload['email']:
|
if db_user['email'] != payload['email']:
|
||||||
my_log.info('User changed email from %s to %s', payload['old_email'], payload['email'])
|
my_log.info('User changed email from %s to %s', payload['old_email'], payload['email'])
|
||||||
updates['email'] = payload['email']
|
updates['email'] = payload['email']
|
||||||
|
db_user['email'] = payload['email']
|
||||||
|
|
||||||
if payload['full_name'] != db_user['full_name']:
|
if db_user['full_name'] != payload['full_name']:
|
||||||
my_log.info('User changed full name from %r to %r',
|
my_log.info('User changed full name from %r to %r',
|
||||||
payload['full_name'], db_user['full_name'])
|
db_user['full_name'], payload['full_name'])
|
||||||
if payload['full_name']:
|
if payload['full_name']:
|
||||||
updates['full_name'] = payload['full_name']
|
updates['full_name'] = payload['full_name']
|
||||||
else:
|
else:
|
||||||
# Fall back to the username when the full name was erased.
|
# Fall back to the username when the full name was erased.
|
||||||
updates['full_name'] = db_user['username']
|
updates['full_name'] = db_user['username']
|
||||||
|
db_user['full_name'] = updates['full_name']
|
||||||
|
|
||||||
if updates:
|
if updates:
|
||||||
users_coll = current_app.db('users')
|
users_coll = current_app.db('users')
|
||||||
|
@ -55,6 +55,12 @@ gulp.task('templates', function() {
|
|||||||
}))
|
}))
|
||||||
.pipe(gulp.dest(destination.pug))
|
.pipe(gulp.dest(destination.pug))
|
||||||
.pipe(gulpif(argv.livereload, livereload()));
|
.pipe(gulpif(argv.livereload, livereload()));
|
||||||
|
// TODO(venomgfx): please check why 'gulp watch' doesn't pick up on .txt changes.
|
||||||
|
gulp.src('src/templates/**/*.txt')
|
||||||
|
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||||
|
.pipe(gulpif(enabled.cachify, cache('templating')))
|
||||||
|
.pipe(gulp.dest(destination.pug))
|
||||||
|
.pipe(gulpif(argv.livereload, livereload()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
97
src/templates/emails/layout.pug
Normal file
97
src/templates/emails/layout.pug
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title {% block title %}{{ subject }}{% endblock %}
|
||||||
|
style.
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Roboto');
|
||||||
|
html, body {
|
||||||
|
font-family: 'Roboto', 'Noto Sans', sans-serif;
|
||||||
|
font-size: 11pt;
|
||||||
|
background-color: #eaebec;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
section, h1 {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 522px;
|
||||||
|
margin: 15px auto;
|
||||||
|
padding: 10px 25px;
|
||||||
|
box-shadow: rgba(0,0,0,0.298039) 0px 1px 4px -1px;
|
||||||
|
}
|
||||||
|
a:link {
|
||||||
|
color: #2e99b8;
|
||||||
|
}
|
||||||
|
a:link, a:visited {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
section.about, p.ps {
|
||||||
|
color: #888;
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
section.about a:link, p.ps a:link {
|
||||||
|
color: #7297ab;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-shadow: 1px 1px 1px rgba(0,0,0,0.5), 0 0 25px rgba(0,0,0,0.6);
|
||||||
|
color: white;
|
||||||
|
font-size: 30px;
|
||||||
|
background: #d5d0cb url('{{ abs_url("static", filename="assets/img/email/background_caminandes_3_03.jpg") }}') no-repeat top center;
|
||||||
|
-webkit-background-size: cover;
|
||||||
|
-moz-background-size: cover;
|
||||||
|
-o-background-size: cover;
|
||||||
|
background-size: cover;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-weight: 300;
|
||||||
|
color: #eb5e28;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 150%;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
p.closing {
|
||||||
|
margin-top: 2.5em;
|
||||||
|
}
|
||||||
|
p.closing a.cloudlogo {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
p.buttons {
|
||||||
|
text-align: center;
|
||||||
|
margin: 5ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 30ex;
|
||||||
|
padding: 5px 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
border: thin solid #2e99b8;
|
||||||
|
color: #2e99b8;
|
||||||
|
background-color: transparent;
|
||||||
|
text-shadow: none;
|
||||||
|
transition: color 350ms ease-out, border 150ms ease-in-out, opacity 150ms ease-in-out, background-color 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
a.button:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: #2e99b8;
|
||||||
|
}
|
||||||
|
img#blendercloud {
|
||||||
|
height: 6em;
|
||||||
|
width: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-top: -0.5em;
|
||||||
|
margin-bottom: -0.2em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
body
|
||||||
|
| {% block body %}{% endblock %}
|
1
src/templates/emails/layout.txt
Normal file
1
src/templates/emails/layout.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% block body %}{% endblock %}
|
72
src/templates/emails/welcome.pug
Normal file
72
src/templates/emails/welcome.pug
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
| {% extends "emails/layout.html" %}
|
||||||
|
| {% block body %}
|
||||||
|
| {% set blender_cloud = abs_url('cloud.homepage') %}
|
||||||
|
h1 Welcome to Blender Cloud
|
||||||
|
section
|
||||||
|
h2 Hi {{ user.full_name or user.email }}
|
||||||
|
|
||||||
|
p.
|
||||||
|
Sybren here. I'm one of the Blender Cloud developers, and I'm very happy to welcome you. The
|
||||||
|
Blender Animation Studio projects are made possible thanks to you!
|
||||||
|
#[strong In this email I'll explain where to go and what to see.]
|
||||||
|
If you want to dive in now, just hit this button:
|
||||||
|
|
||||||
|
p.buttons
|
||||||
|
a.button(href="{{ blender_cloud }}", target='_blank') Log in and Explore!
|
||||||
|
|
||||||
|
p.
|
||||||
|
To get the most out of Blender Cloud, be sure to check out
|
||||||
|
#[a(href="{{ abs_url('cloud.services') }}", target='_blank') the services] we offer, like
|
||||||
|
accessing our texture library from within Blender using the Cloud add-on, Blender Sync,
|
||||||
|
Image Sharing, and more:
|
||||||
|
|
||||||
|
p
|
||||||
|
ul
|
||||||
|
li #[a(href="{{ abs_url('cloud.courses') }}", target='_blank') Courses] & #[a(href="{{ abs_url('cloud.workshops') }}", target='_blank') Workshops]
|
||||||
|
li #[a(href="{{ abs_url('projects.view', project_url='characters') }}", target='_blank') Characters] & #[a(href="{{ abs_url('cloud.open_projects') }}", target='_blank') Open Movies]
|
||||||
|
li #[a(href="{{ abs_url('projects.view', project_url='textures') }}", target='_blank') Textures] & #[a(href="{{ abs_url('projects.view', project_url='hdri') }}", target='_blank') HDRIs]
|
||||||
|
li #[a(href="{{ abs_url('projects.view', project_url='gallery') }}", target='_blank') Art Gallery]
|
||||||
|
li #[a(href="{{ abs_url('projects.index') }}", target='_blank') Your own Projects]
|
||||||
|
|
||||||
|
p.
|
||||||
|
If you have any questions, remarks, ideas, or complaints, you can contact us at
|
||||||
|
#[a(href='mailto:cloudsupport@blender.org') cloudsupport@blender.org]. We're always
|
||||||
|
happy to help, and on working days we'll get back to you within a day.
|
||||||
|
|
||||||
|
p.
|
||||||
|
To update your personal details, such as your email address, password, or full name, please
|
||||||
|
visit #[a(href="https://www.blender.org/id/") Blender ID].
|
||||||
|
|
||||||
|
p.
|
||||||
|
For renewing your subscription and other monetary things, there is the
|
||||||
|
#[a(href='https://store.blender.org/my-account/subscriptions/') Subscription Overview at the Blender Store].
|
||||||
|
|
||||||
|
p.closing
|
||||||
|
a.cloudlogo(href="{{ blender_cloud }}")
|
||||||
|
img#blendercloud(src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MDMuNSAzODkuMyI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTk0LjM3IC0xLjU2MykiPjxwYXRoIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Im0yMjguMSAzMzQuNWMtMzguNzQtOC4zOS03MS4xLTQxLjY0LTc4LjY4LTgwLjg1LTQuNjI2LTIzLjk1IDAuNjcxMS01My42IDEzLjE5LTczLjg1bDUuNTIxLTguOTI1LTkuMy02LjY4MmMtNS4xMTUtMy42NzUtOS4zLTcuMzg1LTkuMy04LjI0NCAwLTEuNTA3IDcuMzcxLTMuMjc1IDMwLjU3LTcuMzM1IDEwLjQyLTEuODI1IDE4LTAuNDY4MiAxOC4wOSAzLjIzNSAwLjAxODMgMC43MzUyIDEuNjA1IDEwLjAzIDMuNTMxIDIwLjY1IDQuMTExIDIyLjY4IDMuMDY1IDI0LjM3LTkuNDU1IDE1LjNsLTguMTAzLTUuODctNC4yMzkgNS4yNjhjLTkuMTU5IDExLjM4LTEyLjU2IDI0LjE5LTEyLjU4IDQ3LjM1LTAuMDEyNyAyMC42OCAwLjIyODYgMjIuMDIgNi4zNTYgMzQuNiAxMS43OCAyNC4yMSAzNS41NyA0My4wNiA2MC4yMyA0Ny43NCA1LjUyOSAxLjA0NyA1My4yMiAxLjgyNSAxMTIgMS44MjUgNTguNzcgMCAxMDYuNS0wLjc3OCAxMTItMS44MjUgMjQuNzUtNC42OTUgNDguNjEtMjMuNjQgNjAuMDgtNDcuNzEgNS4zNTYtMTEuMjQgNi4zNDMtMTUuNTYgNy4wNi0zMC44NiAwLjY5NDYtMTQuODUgMC4xNDk0LTE5Ljc5LTMuMjU1LTI5Ljg1LTExLjE0LTMyLjg1LTM4LjYzLTU1LjU2LTY5LjcyLTU3LjYtMTYuNjEtMS4wOTItMjEuMTMtMy42NTgtMjIuODYtMTMtMy41MjItMTguOTUtMjMuODgtNDUuNS00MS45OS01NC43My0yMi40OS0xMS40Ny01MS4zLTEyLjQ2LTczLjExLTIuNTE1LTM0LjMyIDE1LjY1LTU1LjAxIDUzLjU4LTQ4Ljc3IDg5LjQxIDcuMDQxIDQwLjQzIDQxLjg4IDcwLjE0IDgyLjI3IDcwLjE0IDEwLjQ1IDAgMjkuMDgtMy42OTQgMzQuMTktNi43ODEgMC43MDUzLTAuNDI3My0wLjc4MjMtNS40NDUtMy4zMDYtMTEuMTUtMi41MjQtNS43MDgtNC4xMzItMTEuMTItMy41NzItMTIuMDIgMC41NjAyLTAuOTA0MSA4Ljc4MSAxLjE4MiAxOC4yNyA0LjY0MSAyMy45NSA4LjcyNSAyMy4zOSA4LjQ2NyAyNC41MyAxMS40MyAxLjIyNSAzLjE4OS0xMi42NiA0Mi44OC0xNSA0Mi44OC0wLjg5NzcgMC0zLjk3My00LjM2NS02LjgzMi05LjcwMWwtNS4xOTktOS43MDEtMTAuNyAzLjc4OGMtMTUuMTcgNS4zNjktMzkuMzggNi41NzQtNTQuNjYgMi43MTktMzguNDUtOS43MDEtNjguMDUtMzguOTctNzYuODQtNzUuOTUtOC4wODUtMzQuMDUgMi4wMzMtNjkuMjUgMjcuMi05NC42NSAxOS45My0yMC4xIDQ0Ljc3LTMwLjY4IDcyLjA4LTMwLjY4IDQxLjYzIDAgODIuNDIgMjguMjcgOTUuMjUgNjYuMDJsMy4yNCA5LjUzMSAxMy43NCAyLjMxNWMzOC43MyA2LjUyNyA3MS4zNCAzNy4xNSA4MS4zMyA3Ni4zNyAxNC4wOSA1NS4zNC0yMi4zNSAxMTMuNS03OC41MyAxMjUuNC0xOC42OSAzLjk0MS0yMTYuNSAzLjg1My0yMzQuNy0wLjEwNjd6IiBzdHJva2U9IiM5MmM2ZDQiIHN0cm9rZS13aWR0aD0iNi43OTYiIGZpbGw9IiM5MmM2ZDQiLz48L2c+PC9zdmc+Cg==",
|
||||||
|
alt="Blender Cloud Logo")
|
||||||
|
| Warm regards,
|
||||||
|
p.
|
||||||
|
Sybren A. Stüvel #[br] and the entire Blender Cloud team
|
||||||
|
|
||||||
|
p.ps.
|
||||||
|
PS: If you do not want to receive other emails from us,
|
||||||
|
#[a(href="{{ abs_url('settings.emails') }}") we've got you covered].
|
||||||
|
|
||||||
|
section.about
|
||||||
|
h2 About Blender Cloud
|
||||||
|
|
||||||
|
p.
|
||||||
|
Blender Cloud is the creative hub for your projects, powered by Free and Open Source
|
||||||
|
software.
|
||||||
|
|
||||||
|
p.
|
||||||
|
On Blender Cloud you can create and share personal projects, access our texture and HDRI
|
||||||
|
library, keep track of your production, manage your renders, and much more!
|
||||||
|
|
||||||
|
p.
|
||||||
|
The Blender Animation Studio projects are made possible thanks to subscriptions to the
|
||||||
|
#[a(href='{{ blender_cloud }}') Blender Cloud]. Thanks for joining!
|
||||||
|
|
||||||
|
|
||||||
|
| {% endblock %}
|
53
src/templates/emails/welcome.txt
Normal file
53
src/templates/emails/welcome.txt
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{% extends "emails/layout.txt" %}
|
||||||
|
{% set blender_cloud = abs_url('cloud.homepage') %}
|
||||||
|
{% block body %}Welcome to Blender Cloud, {{ user.full_name or user.nickname }}!
|
||||||
|
|
||||||
|
Sybren here. I'm one of the Blender Cloud developers, and I'm very
|
||||||
|
happy to welcome you. The Blender Animation Studio projects are
|
||||||
|
made possible thanks to you! In this email I'll explain where to
|
||||||
|
go and what to see. If you want to dive in now, just log in and
|
||||||
|
explore at:
|
||||||
|
|
||||||
|
{{ blender_cloud }}
|
||||||
|
|
||||||
|
To get the most out of Blender Cloud, be sure to check out the
|
||||||
|
services we offer at, like accessing our texture library from
|
||||||
|
within Blender using the Cloud add-on, Blender Sync, and Image
|
||||||
|
Sharing:
|
||||||
|
{{ abs_url('cloud.services') }}
|
||||||
|
|
||||||
|
|
||||||
|
If you have any questions, remarks, ideas, or complaints, you can
|
||||||
|
contact us at cloudsupport@blender.org. We're always happy to
|
||||||
|
help, and on working days we'll get back to you within a day.
|
||||||
|
|
||||||
|
To update your personal details, such as your email address,
|
||||||
|
password, or full name, please visit Blender ID at:
|
||||||
|
https://www.blender.org/id/
|
||||||
|
|
||||||
|
For renewing your subscription and other monetary things, there is
|
||||||
|
the Subscription Overview at the Blender Store:
|
||||||
|
https://store.blender.org/my-account/subscriptions/
|
||||||
|
|
||||||
|
Warm regards,
|
||||||
|
|
||||||
|
Sybren A. Stüvel
|
||||||
|
and the entire Blender Cloud team
|
||||||
|
|
||||||
|
PS: If you do not want to receive other emails from us, we've got
|
||||||
|
you covered: {{ abs_url('settings.emails') }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
About Blender Cloud
|
||||||
|
|
||||||
|
Blender Cloud is the creative hub for your projects, powered by
|
||||||
|
Free and Open Source software.
|
||||||
|
|
||||||
|
On Blender Cloud you can create and share personal projects,
|
||||||
|
access our texture and HDRI library, keep track of your
|
||||||
|
production, manage your renders, and much more!
|
||||||
|
|
||||||
|
The Blender Animation Studio projects are made possible thanks to
|
||||||
|
subscriptions to the Blender Cloud. Thanks for joining!
|
||||||
|
{% endblock %}
|
BIN
static/assets/img/email/background_caminandes_3_03.jpg
Normal file
BIN
static/assets/img/email/background_caminandes_3_03.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -371,7 +371,7 @@ class UserModifiedUserCreationTest(AbstractWebhookTest):
|
|||||||
self.assertIsNotNone(new_user)
|
self.assertIsNotNone(new_user)
|
||||||
self.assertEqual('new', new_user['username'])
|
self.assertEqual('new', new_user['username'])
|
||||||
self.assertEqual('ကြယ်ဆွတ်', new_user['full_name'])
|
self.assertEqual('ကြယ်ဆွတ်', new_user['full_name'])
|
||||||
self.assertEqual(['subscriber', 'has_subscription'], new_user['roles'])
|
self.assertEqual({'subscriber', 'has_subscription'}, set(new_user['roles']))
|
||||||
|
|
||||||
def test_create_renewable(self):
|
def test_create_renewable(self):
|
||||||
payload = {'id': 1112333,
|
payload = {'id': 1112333,
|
||||||
@ -423,7 +423,7 @@ class UserModifiedUserCreationTest(AbstractWebhookTest):
|
|||||||
self.assertIsNotNone(new_user)
|
self.assertIsNotNone(new_user)
|
||||||
self.assertEqual('new', new_user['username'])
|
self.assertEqual('new', new_user['username'])
|
||||||
self.assertEqual('new', new_user['full_name']) # defaults to username
|
self.assertEqual('new', new_user['full_name']) # defaults to username
|
||||||
self.assertEqual(['subscriber', 'has_subscription'], new_user['roles'])
|
self.assertEqual({'subscriber', 'has_subscription'}, set(new_user['roles']))
|
||||||
|
|
||||||
def test_no_create_when_not_subscriber(self):
|
def test_no_create_when_not_subscriber(self):
|
||||||
"""Don't create local users when they are not subscriber."""
|
"""Don't create local users when they are not subscriber."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user