Merge branch '839-donation-links' into 'develop'
Resolve "Add optional donation/contribution link in-app" Closes #839 See merge request funkwhale/funkwhale!900
This commit is contained in:
commit
a0c99c3f83
|
@ -884,3 +884,7 @@ FEDERATION_OBJECT_FETCH_DELAY = env.int(
|
|||
MODERATION_EMAIL_NOTIFICATIONS_ENABLED = env.bool(
|
||||
"MODERATION_EMAIL_NOTIFICATIONS_ENABLED", default=True
|
||||
)
|
||||
|
||||
# Delay in days after signup before we show the "support us" messages
|
||||
INSTANCE_SUPPORT_MESSAGE_DELAY = env.int("INSTANCE_SUPPORT_MESSAGE_DELAY", default=15)
|
||||
FUNKWHALE_SUPPORT_MESSAGE_DELAY = env.int("FUNKWHALE_SUPPORT_MESSAGE_DELAY", default=15)
|
||||
|
|
|
@ -82,6 +82,35 @@ class InstanceContactEmail(types.StringPreference):
|
|||
field_kwargs = {"required": False}
|
||||
|
||||
|
||||
@global_preferences_registry.register
|
||||
class InstanceSupportMessage(types.StringPreference):
|
||||
show_in_api = True
|
||||
section = instance
|
||||
name = "support_message"
|
||||
verbose_name = "Support message"
|
||||
default = ""
|
||||
help_text = (
|
||||
"A short message that will be displayed periodically to local users. "
|
||||
"Use it to ask for financial support or anything else you might need. "
|
||||
"(markdown allowed)."
|
||||
)
|
||||
widget = widgets.Textarea
|
||||
field_kwargs = {"required": False}
|
||||
|
||||
|
||||
@global_preferences_registry.register
|
||||
class InstanceFunkwhaleSupportMessageEnabled(types.BooleanPreference):
|
||||
show_in_api = True
|
||||
section = instance
|
||||
name = "funkwhale_support_message_enabled"
|
||||
verbose_name = "Funkwhale Support message"
|
||||
default = True
|
||||
help_text = (
|
||||
"If this is enabled, we will periodically display a message to encourage "
|
||||
"local users to support Funkwhale."
|
||||
)
|
||||
|
||||
|
||||
@global_preferences_registry.register
|
||||
class RavenDSN(types.StringPreference):
|
||||
show_in_api = True
|
||||
|
|
|
@ -63,6 +63,10 @@ def get():
|
|||
{"type": t, "label": l, "anonymous": t in unauthenticated_report_types}
|
||||
for t, l in moderation_models.REPORT_TYPES
|
||||
],
|
||||
"funkwhaleSupportMessageEnabled": all_preferences.get(
|
||||
"instance__funkwhale_support_message_enabled"
|
||||
),
|
||||
"instanceSupportMessage": all_preferences.get("instance__support_message"),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,15 @@ class UserAdmin(AuthUserAdmin):
|
|||
},
|
||||
),
|
||||
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
||||
(
|
||||
_("Other"),
|
||||
{
|
||||
"fields": (
|
||||
"instance_support_message_display_date",
|
||||
"funkwhale_support_message_display_date",
|
||||
)
|
||||
},
|
||||
),
|
||||
(_("Useless fields"), {"fields": ("user_permissions", "groups")}),
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# Generated by Django 2.2.4 on 2019-09-20 08:57
|
||||
|
||||
import datetime
|
||||
from django.conf import settings
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
import funkwhale_api.users.models
|
||||
|
||||
|
||||
def set_display_date(apps, schema_editor):
|
||||
"""
|
||||
Set display date for instance/funkwhale support message on existing users
|
||||
"""
|
||||
User = apps.get_model("users", "User")
|
||||
now = django.utils.timezone.now()
|
||||
instance_support_message_display_date = now + datetime.timedelta(days=settings.INSTANCE_SUPPORT_MESSAGE_DELAY)
|
||||
funkwhale_support_message_display_date = now + datetime.timedelta(days=settings.FUNKWHALE_SUPPORT_MESSAGE_DELAY)
|
||||
|
||||
User.objects.update(instance_support_message_display_date=instance_support_message_display_date)
|
||||
User.objects.update(funkwhale_support_message_display_date=funkwhale_support_message_display_date)
|
||||
|
||||
|
||||
def rewind(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0015_application_scope'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='funkwhale_support_message_display_date',
|
||||
field=models.DateTimeField(blank=True, null=True, default=funkwhale_api.users.models.get_default_funkwhale_support_message_display_date),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='instance_support_message_display_date',
|
||||
field=models.DateTimeField(blank=True, null=True, default=funkwhale_api.users.models.get_default_instance_support_message_display_date),
|
||||
),
|
||||
migrations.RunPython(set_display_date, rewind),
|
||||
]
|
|
@ -82,6 +82,18 @@ PERMISSIONS = sorted(PERMISSIONS_CONFIGURATION.keys())
|
|||
get_file_path = common_utils.ChunkedPath("users/avatars", preserve_file_name=False)
|
||||
|
||||
|
||||
def get_default_instance_support_message_display_date():
|
||||
return timezone.now() + datetime.timedelta(
|
||||
days=settings.INSTANCE_SUPPORT_MESSAGE_DELAY
|
||||
)
|
||||
|
||||
|
||||
def get_default_funkwhale_support_message_display_date():
|
||||
return timezone.now() + datetime.timedelta(
|
||||
days=settings.FUNKWHALE_SUPPORT_MESSAGE_DELAY
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class User(AbstractUser):
|
||||
|
||||
|
@ -149,6 +161,15 @@ class User(AbstractUser):
|
|||
|
||||
upload_quota = models.PositiveIntegerField(null=True, blank=True)
|
||||
|
||||
instance_support_message_display_date = models.DateTimeField(
|
||||
default=get_default_instance_support_message_display_date, null=True, blank=True
|
||||
)
|
||||
funkwhale_support_message_display_date = models.DateTimeField(
|
||||
default=get_default_funkwhale_support_message_display_date,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
|
|
|
@ -109,7 +109,13 @@ class UserWriteSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ["name", "privacy_level", "avatar"]
|
||||
fields = [
|
||||
"name",
|
||||
"privacy_level",
|
||||
"avatar",
|
||||
"instance_support_message_display_date",
|
||||
"funkwhale_support_message_display_date",
|
||||
]
|
||||
|
||||
|
||||
class UserReadSerializer(serializers.ModelSerializer):
|
||||
|
@ -146,7 +152,11 @@ class MeSerializer(UserReadSerializer):
|
|||
quota_status = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(UserReadSerializer.Meta):
|
||||
fields = UserReadSerializer.Meta.fields + ["quota_status"]
|
||||
fields = UserReadSerializer.Meta.fields + [
|
||||
"quota_status",
|
||||
"instance_support_message_display_date",
|
||||
"funkwhale_support_message_display_date",
|
||||
]
|
||||
|
||||
def get_quota_status(self, o):
|
||||
return o.get_quota_status() if o.actor else 0
|
||||
|
|
|
@ -87,6 +87,10 @@ def test_nodeinfo_dump(preferences, mocker, avatar):
|
|||
},
|
||||
{"type": "other", "label": "Other", "anonymous": True},
|
||||
],
|
||||
"funkwhaleSupportMessageEnabled": preferences[
|
||||
"instance__funkwhale_support_message_enabled"
|
||||
],
|
||||
"instanceSupportMessage": preferences["instance__support_message"],
|
||||
},
|
||||
}
|
||||
assert nodeinfo.get() == expected
|
||||
|
@ -151,6 +155,10 @@ def test_nodeinfo_dump_stats_disabled(preferences, mocker):
|
|||
},
|
||||
{"type": "other", "label": "Other", "anonymous": True},
|
||||
],
|
||||
"funkwhaleSupportMessageEnabled": preferences[
|
||||
"instance__funkwhale_support_message_enabled"
|
||||
],
|
||||
"instanceSupportMessage": preferences["instance__support_message"],
|
||||
},
|
||||
}
|
||||
assert nodeinfo.get() == expected
|
||||
|
|
|
@ -220,3 +220,20 @@ def test_user_get_quota_status(factories, preferences, mocker):
|
|||
"errored": 3,
|
||||
"finished": 4,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"setting_name, field",
|
||||
[
|
||||
("INSTANCE_SUPPORT_MESSAGE_DELAY", "instance_support_message_display_date"),
|
||||
("FUNKWHALE_SUPPORT_MESSAGE_DELAY", "funkwhale_support_message_display_date"),
|
||||
],
|
||||
)
|
||||
def test_creating_user_set_support_display_date(
|
||||
setting_name, field, settings, factories, now
|
||||
):
|
||||
setattr(settings, setting_name, 66) # display message every 66 days
|
||||
expected = now + datetime.timedelta(days=66)
|
||||
user = factories["users.User"]()
|
||||
|
||||
assert getattr(user, field) == expected
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Added periodical message to incite people to support their pod and Funkwhale (#839)
|
|
@ -113,6 +113,16 @@ your pod.
|
|||
|
||||
If you want to enable this feature on your pod, or learn more, please refer to `our documentation <https://docs.funkwhale.audio/moderator/listing.html>`_!
|
||||
|
||||
Periodic message to incite people to support their pod and Funkwhale
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Users will now be reminded on a regular basis that they can help Funkwhale by donating or contributing.
|
||||
|
||||
If specified by the pod admin, a separate and custom message will also be displayed in a similar way to provide instructions and links to support the pod.
|
||||
|
||||
Both messages will appear for the first time 15 days after signup, in the notifications tab. For each message, users can schedule a reminder for a later time, or disable the messages entirely.
|
||||
|
||||
|
||||
Replaced Daphne by Gunicorn/Uvicorn [manual action required, non-docker only]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -46,9 +46,9 @@
|
|||
<i class="feed icon"></i>
|
||||
<translate translate-context="*/Notifications/*">Notifications</translate>
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.inbox > 0"
|
||||
v-if="$store.state.ui.notifications.inbox + additionalNotifications > 0"
|
||||
:class="['ui', 'teal', 'label']">
|
||||
{{ $store.state.ui.notifications.inbox }}</div>
|
||||
{{ $store.state.ui.notifications.inbox + additionalNotifications }}</div>
|
||||
</router-link>
|
||||
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i><translate translate-context="Sidebar/Login/List item.Link/Verb">Logout</translate></router-link>
|
||||
<template v-else>
|
||||
|
@ -186,7 +186,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions } from "vuex"
|
||||
import { mapState, mapActions, mapGetters } from "vuex"
|
||||
|
||||
import Player from "@/components/audio/Player"
|
||||
import Logo from "@/components/Logo"
|
||||
|
@ -219,6 +219,9 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
additionalNotifications: "ui/additionalNotifications",
|
||||
}),
|
||||
...mapState({
|
||||
queue: state => state.queue,
|
||||
url: state => state.route.path
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import Vue from 'vue'
|
||||
import axios from 'axios'
|
||||
import jwtDecode from 'jwt-decode'
|
||||
import logger from '@/logging'
|
||||
import router from '@/router'
|
||||
import lodash from '@/lodash'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
|
@ -73,6 +75,11 @@ export default {
|
|||
},
|
||||
permission: (state, {key, status}) => {
|
||||
state.availablePermissions[key] = status
|
||||
},
|
||||
profilePartialUpdate: (state, payload) => {
|
||||
lodash.keys(payload).forEach((k) => {
|
||||
Vue.set(state.profile, k, payload[k])
|
||||
})
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
|
|
@ -28,6 +28,12 @@ export default {
|
|||
},
|
||||
long_description: {
|
||||
value: ''
|
||||
},
|
||||
funkwhale_support_message_enabled: {
|
||||
value: true
|
||||
},
|
||||
support_message: {
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
users: {
|
||||
|
|
|
@ -26,6 +26,44 @@ export default {
|
|||
},
|
||||
pageTitle: null
|
||||
},
|
||||
getters: {
|
||||
showInstanceSupportMessage: (state, getters, rootState) => {
|
||||
if (!rootState.auth.profile) {
|
||||
return false
|
||||
}
|
||||
if (!rootState.instance.settings.instance.support_message.value) {
|
||||
return false
|
||||
}
|
||||
let displayDate = rootState.auth.profile.instance_support_message_display_date
|
||||
if (!displayDate) {
|
||||
return false
|
||||
}
|
||||
return moment(displayDate) < moment(state.lastDate)
|
||||
},
|
||||
showFunkwhaleSupportMessage: (state, getters, rootState) => {
|
||||
if (!rootState.auth.profile) {
|
||||
return false
|
||||
}
|
||||
if (!rootState.instance.settings.instance.funkwhale_support_message_enabled.value) {
|
||||
return false
|
||||
}
|
||||
let displayDate = rootState.auth.profile.funkwhale_support_message_display_date
|
||||
if (!displayDate) {
|
||||
return false
|
||||
}
|
||||
return moment(displayDate) < moment(state.lastDate)
|
||||
},
|
||||
additionalNotifications: (state, getters) => {
|
||||
let count = 0
|
||||
if (getters.showInstanceSupportMessage) {
|
||||
count += 1
|
||||
}
|
||||
if (getters.showFunkwhaleSupportMessage) {
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
addWebsocketEventHandler: (state, {eventName, id, handler}) => {
|
||||
state.websocketEventsHandlers[eventName][id] = handler
|
||||
|
|
|
@ -2,6 +2,71 @@
|
|||
<main class="main pusher" v-title="labels.title">
|
||||
<section class="ui vertical aligned stripe segment">
|
||||
<div class="ui container">
|
||||
<div class="ui container" v-if="additionalNotifications">
|
||||
<h1 class="ui header"><translate translate-context="Content/Notifications/Title">Your messages</translate></h1>
|
||||
<div class="ui two column stackable grid">
|
||||
<div class="column" v-if="showInstanceSupportMessage">
|
||||
<div class="ui attached info message">
|
||||
<div class="header">
|
||||
<translate translate-context="Content/Notifications/Header">Support this Funkwhale pod</translate>
|
||||
</div>
|
||||
<div v-html="markdown.makeHtml($store.state.instance.settings.instance.support_message.value)"></div>
|
||||
</div>
|
||||
<div class="ui bottom attached segment">
|
||||
<form @submit.prevent="setDisplayDate('instance_support_message_display_date', instanceSupportMessageDelay)" class="ui inline form">
|
||||
<div class="inline field">
|
||||
<label>
|
||||
<translate translate-context="Content/Notifications/Label">Remind me in:</translate>
|
||||
</label>
|
||||
<select v-model="instanceSupportMessageDelay">
|
||||
<option :value="30"><translate translate-context="*/*/*">30 days</translate></option>
|
||||
<option :value="60"><translate translate-context="*/*/*">60 days</translate></option>
|
||||
<option :value="90"><translate translate-context="*/*/*">90 days</translate></option>
|
||||
<option :value="null"><translate translate-context="*/*/*">Never</translate></option>
|
||||
</select>
|
||||
<button type="submit" class="ui right floated basic button">
|
||||
<translate translate-context="Content/Notifications/Button.Label">Got it!</translate>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column" v-if="showFunkwhaleSupportMessage">
|
||||
<div class="ui info attached message">
|
||||
<div class="header">
|
||||
<translate translate-context="Content/Notifications/Header">Do you like Funkwhale?</translate>
|
||||
</div>
|
||||
<p>
|
||||
<translate translate-context="Content/Notifications/Paragraph">We noticed you've been here for a while. If Funkwhale is useful to you, we could use your help to make it even better!</translate>
|
||||
</p>
|
||||
<a href="https://funkwhale.audio/support-us" _target="blank" rel="noopener" class="ui primary inverted button">
|
||||
<translate translate-context="Content/Notifications/Button.Label/Verb">Donate</translate>
|
||||
</a>
|
||||
<a href="https://contribute.funkwhale.audio" _target="blank" rel="noopener" class="ui secondary inverted button">
|
||||
<translate translate-context="Content/Notifications/Button.Label/Verb">Discover other ways to help</translate>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ui bottom attached segment">
|
||||
<form @submit.prevent="setDisplayDate('funkwhale_support_message_display_date', funkwhaleSupportMessageDelay)" class="ui inline form">
|
||||
<div class="inline field">
|
||||
<label>
|
||||
<translate translate-context="Content/Notifications/Label">Remind me in:</translate>
|
||||
</label>
|
||||
<select v-model="funkwhaleSupportMessageDelay">
|
||||
<option :value="30"><translate translate-context="*/*/*">30 days</translate></option>
|
||||
<option :value="60"><translate translate-context="*/*/*">60 days</translate></option>
|
||||
<option :value="90"><translate translate-context="*/*/*">90 days</translate></option>
|
||||
<option :value="null"><translate translate-context="*/*/*">Never</translate></option>
|
||||
</select>
|
||||
<button type="submit" class="ui right floated basic button">
|
||||
<translate translate-context="Content/Notifications/Button.Label">Got it!</translate>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="ui header"><translate translate-context="Content/Notifications/Title">Your notifications</translate></h1>
|
||||
<div class="ui toggle checkbox">
|
||||
<input v-model="filters.is_read" type="checkbox">
|
||||
|
@ -25,7 +90,7 @@
|
|||
<notification-row :item="item" v-for="item in notifications.results" :key="item.id" />
|
||||
</tbody>
|
||||
</table>
|
||||
<p v-else>
|
||||
<p v-else-if="additionalNotifications === 0">
|
||||
<translate translate-context="Content/Notifications/Paragraph">No notification to show.</translate>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -34,9 +99,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex"
|
||||
import { mapState, mapGetters } from "vuex"
|
||||
import axios from "axios"
|
||||
import logger from "@/logging"
|
||||
import showdown from 'showdown'
|
||||
import moment from 'moment'
|
||||
|
||||
import NotificationRow from "@/components/notifications/NotificationRow"
|
||||
|
||||
|
@ -44,7 +111,10 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
markdown: new showdown.Converter(),
|
||||
notifications: {count: 0, results: []},
|
||||
instanceSupportMessageDelay: 60,
|
||||
funkwhaleSupportMessageDelay: 60,
|
||||
filters: {
|
||||
is_read: false
|
||||
}
|
||||
|
@ -71,6 +141,11 @@ export default {
|
|||
...mapState({
|
||||
events: state => state.instance.events
|
||||
}),
|
||||
...mapGetters({
|
||||
additionalNotifications: 'ui/additionalNotifications',
|
||||
showInstanceSupportMessage: 'ui/showInstanceSupportMessage',
|
||||
showFunkwhaleSupportMessage: 'ui/showFunkwhaleSupportMessage',
|
||||
}),
|
||||
labels() {
|
||||
return {
|
||||
title: this.$pgettext('*/Notifications/*', "Notifications")
|
||||
|
@ -82,6 +157,20 @@ export default {
|
|||
this.notifications.count += 1
|
||||
this.notifications.results.unshift(event.item)
|
||||
},
|
||||
setDisplayDate (field, days) {
|
||||
let payload = {}
|
||||
let newDisplayDate
|
||||
if (days) {
|
||||
newDisplayDate = moment().add({days})
|
||||
} else {
|
||||
newDisplayDate = null
|
||||
}
|
||||
payload[field] = newDisplayDate
|
||||
let self = this
|
||||
axios.patch(`users/users/${this.$store.state.auth.username}/`, payload).then((response) => {
|
||||
self.$store.commit('auth/profilePartialUpdate', response.data)
|
||||
})
|
||||
},
|
||||
fetch(params) {
|
||||
this.isLoading = true
|
||||
let self = this
|
||||
|
|
|
@ -99,6 +99,7 @@ export default {
|
|||
"instance__rules",
|
||||
"instance__terms",
|
||||
"instance__banner",
|
||||
"instance__support_message"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -152,7 +153,7 @@ export default {
|
|||
{
|
||||
label: uiLabel,
|
||||
id: "ui",
|
||||
settings: ["ui__custom_css"]
|
||||
settings: ["ui__custom_css", "instance__funkwhale_support_message_enabled"]
|
||||
},
|
||||
{
|
||||
label: statisticsLabel,
|
||||
|
|
Loading…
Reference in New Issue