Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add donation attribute to appinfo/info.xml, donation buttons to app details page #1452

Merged
merged 7 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/developer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ A full blown example would look like this (needs to be utf-8 encoded):
<repository>https://github.com/nextcloud/news</repository>
<screenshot small-thumbnail="https://example.com/1-small.png">https://example.com/1.png</screenshot>
<screenshot>https://example.com/2.jpg</screenshot>
<donation type="paypal" title="Donate via PayPal">https://paypal.com/example-link</donation>
edward-ly marked this conversation as resolved.
Show resolved Hide resolved
<donation>https://github.com/sponsors/example</donation>
<dependencies>
<php min-version="5.6" min-int-size="64"/>
<database min-version="9.4">pgsql</database>
Expand Down Expand Up @@ -399,6 +401,12 @@ screenshot
* must contain an HTTPS URL to an image
* can contain a **small-thumbnail** attribute which must contain an https url to an image. This image will be used as small preview (e.g. on the app list overview). Keep it small so it renders fast
* will be rendered on the app list and detail page in the given order
donation
* optional
* can occur multiple times containing different donation URLs
* can contain a **title** attribute which must be a string, defaults to **Donate to support this app**
* can contain a **type** attribute, **paypal**, **stripe**, and **other** are allowed values, defaults to **other**
* will be rendered on the app detail page in the given order
dependencies/php
* optional
* can contain a **min-version** attribute (maximum 3 digits separated by dots)
Expand Down
19 changes: 19 additions & 0 deletions nextcloudappstore/api/v1/release/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Category,
Database,
DatabaseDependency,
Donation,
License,
PhpExtension,
PhpExtensionDependency,
Expand Down Expand Up @@ -144,6 +145,21 @@ def create_screenshot(img: dict[str, str]) -> Screenshot:
obj.screenshots.set(list(shots))


class DonationsImporter(ScalarImporter):
def import_data(self, key: str, value: Any, obj: Any) -> None:
def create_donation(img: dict[str, str]) -> Donation:
return Donation.objects.create(
url=img["url"],
app=obj,
ordering=img["ordering"],
title=img["title"],
type=img["type"],
)

shots = map(lambda val: create_donation(val["donation"]), value)
obj.donations.set(list(shots))


class CategoryImporter(ScalarImporter):
def import_data(self, key: str, value: Any, obj: Any) -> None:
def map_categories(cat: dict) -> Category:
Expand Down Expand Up @@ -251,6 +267,7 @@ def __init__(
self,
release_importer: AppReleaseImporter,
screenshots_importer: ScreenshotsImporter,
donations_importer: DonationsImporter,
attribute_importer: StringAttributeImporter,
l10n_importer: L10NImporter,
category_importer: CategoryImporter,
Expand All @@ -261,6 +278,7 @@ def __init__(
{
"release": release_importer,
"screenshots": screenshots_importer,
"donations": donations_importer,
"user_docs": attribute_importer,
"admin_docs": attribute_importer,
"website": attribute_importer,
Expand Down Expand Up @@ -294,6 +312,7 @@ def _before_import(self, key: str, value: Any, obj: Any) -> tuple[Any, Any]:
if self._should_update_everything(value):
# clear all relations
obj.screenshots.all().delete()
obj.donations.all().delete()
obj.authors.all().delete()
obj.categories.clear()
for translation in obj.translations.all():
Expand Down
19 changes: 19 additions & 0 deletions nextcloudappstore/api/v1/release/info.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
maxOccurs="1"/>
<xs:element name="screenshot" type="screenshot" minOccurs="0"
maxOccurs="10"/>
<xs:element name="donation" type="url" minOccurs="0"
maxOccurs="10"/>
<xs:element name="dependencies" type="dependencies"
minOccurs="1" maxOccurs="1"/>
<xs:element name="background-jobs" type="jobs"
Expand Down Expand Up @@ -404,6 +406,23 @@
</xs:sequence>
</xs:complexType>

<xs:simpleType name="donate-platform">
<xs:restriction base="xs:string">
<xs:enumeration value="paypal"/>
<xs:enumeration value="stripe"/>
<xs:enumeration value="other"/>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="donation">
<xs:simpleContent>
<xs:extension base="secure-url">
<xs:attribute name="title" type="limited-string" use="optional"/>
<xs:attribute name="type" type="donate-platform" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="activity">
<xs:sequence>
<xs:element name="settings" type="activity-settings" minOccurs="0"
Expand Down
33 changes: 33 additions & 0 deletions nextcloudappstore/api/v1/release/info.xslt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,39 @@

<!-- optional elements need defaults -->

<donations type="list">
<xsl:for-each select="donation">
<donation>
<url>
<xsl:value-of select="."/>
</url>
<xsl:choose>
<xsl:when test="donation/@title">
<title>
<xsl:value-of select="donation/@title"/>
</title>
</xsl:when>
<xsl:otherwise>
<title>Donate to support this app</title>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="donation/@type">
<type>
<xsl:value-of select="donation/@type"/>
</type>
</xsl:when>
<xsl:otherwise>
<type>other</type>
</xsl:otherwise>
</xsl:choose>
<ordering type="int">
<xsl:value-of select="position()"/>
</ordering>
</donation>
</xsl:for-each>
</donations>

<xsl:if test="documentation/admin[starts-with(., 'https://')]">
<admin-docs>
<xsl:value-of select="documentation/admin"/>
Expand Down
1 change: 1 addition & 0 deletions nextcloudappstore/api/v1/release/pre-info.xslt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<xsl:copy-of select="bugs"/>
<xsl:copy-of select="repository"/>
<xsl:copy-of select="screenshot"/>
<xsl:copy-of select="donation"/>
<xsl:apply-templates select="dependencies"/>
<xsl:copy-of select="background-jobs"/>
<xsl:apply-templates select="repair-steps"/>
Expand Down
2 changes: 2 additions & 0 deletions nextcloudappstore/api/v1/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def test_parse_minimal(self):
"issue_tracker": "https://github.com/nextcloud/news/issues",
"screenshots": [],
"categories": [{"category": {"id": "multimedia"}}],
"donations": [],
"release": {
"databases": [],
"licenses": [{"license": {"id": "agpl"}}],
Expand Down Expand Up @@ -504,6 +505,7 @@ def test_map_data(self):
{"screenshot": {"url": "https://example.com/1.png", "small_thumbnail": None, "ordering": 1}},
{"screenshot": {"url": "https://example.com/2.jpg", "small_thumbnail": None, "ordering": 2}},
],
"donations": [],
}
}
self.assertDictEqual(expected, result)
Expand Down
8 changes: 8 additions & 0 deletions nextcloudappstore/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Category,
Database,
DatabaseDependency,
Donation,
License,
NextcloudRelease,
PhpExtension,
Expand Down Expand Up @@ -136,6 +137,13 @@ class ScreenshotAdmin(admin.ModelAdmin):
list_filter = ("app__id",)


@admin.register(Donation)
class DonationAdmin(admin.ModelAdmin):
ordering = ("app", "ordering")
list_display = ("url", "type", "title", "app", "ordering")
list_filter = ("app__id",)


@admin.register(NextcloudRelease)
class NextcloudReleaseAdmin(admin.ModelAdmin):
list_display = ("version", "is_current", "has_release", "is_supported")
Expand Down
30 changes: 30 additions & 0 deletions nextcloudappstore/core/migrations/0033_donation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.2.14 on 2024-08-13 04:14

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


class Migration(migrations.Migration):

dependencies = [
('core', '0032_app_is_enterprise_supported'),
]

operations = [
migrations.CreateModel(
name='Donation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField(max_length=256, verbose_name='Donation URL')),
('type', models.CharField(default='other', max_length=256, verbose_name='Donation Type')),
('title', models.CharField(default='Donate to support this app', max_length=256, verbose_name='Donation Title')),
('ordering', models.IntegerField(verbose_name='Ordering')),
('app', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='donations', to='core.app', verbose_name='App')),
],
options={
'verbose_name': 'Donation',
'verbose_name_plural': 'Donations',
'ordering': ['ordering'],
},
),
]
16 changes: 16 additions & 0 deletions nextcloudappstore/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,22 @@ def __str__(self) -> str:
return self.url


class Donation(Model):
url = URLField(max_length=256, verbose_name=_("Donation URL"))
type = CharField(max_length=256, verbose_name=_("Donation Type"), default="other")
title = CharField(max_length=256, verbose_name=_("Donation Title"), default="Donate to support this app")
app = ForeignKey("App", on_delete=CASCADE, verbose_name=_("App"), related_name="donations")
ordering = IntegerField(verbose_name=_("Ordering"))

class Meta:
verbose_name = _("Donation")
verbose_name_plural = _("Donations")
ordering = ["ordering"]

def __str__(self) -> str:
return self.url


class ShellCommand(Model):
name = CharField(
max_length=256,
Expand Down
36 changes: 36 additions & 0 deletions nextcloudappstore/core/static/assets/css/icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
--original-icon-comment-question-white: url('../img/icons/detail/comment-question-white.svg');
--original-icon-feature-search-dark: url('../img/icons/detail/feature-search.svg');
--original-icon-feature-search-white: url('../img/icons/detail/feature-search-white.svg');
--original-icon-donate-paypal-dark: url('../img/icons/detail/donate-paypal.svg');
--original-icon-donate-paypal-white: url('../img/icons/detail/donate-paypal-white.svg');
--original-icon-donate-stripe-dark: url('../img/icons/detail/donate-stripe.svg');
--original-icon-donate-stripe-white: url('../img/icons/detail/donate-stripe-white.svg');
--original-icon-donate-other-dark: url('../img/icons/detail/donate-other.svg');
--original-icon-donate-other-white: url('../img/icons/detail/donate-other-white.svg');
--original-icon-send-dark: url('../img/icons/detail/send.svg');
--original-icon-send-white: url('../img/icons/detail/send-white.svg');
--original-icon-relevance-dark: url('../img/icons/list/relevance.svg');
Expand Down Expand Up @@ -107,6 +113,12 @@ body {
--icon-comment-question-white: var(--original-icon-comment-question-white);
--icon-feature-search-dark: var(--original-icon-feature-search-dark);
--icon-feature-search-white: var(--original-icon-feature-search-white);
--icon-donate-paypal-dark: var(--original-icon-donate-paypal-dark);
--icon-donate-paypal-white: var(--original-icon-donate-paypal-white);
--icon-donate-stripe-dark: var(--original-icon-donate-stripe-dark);
--icon-donate-stripe-white: var(--original-icon-donate-stripe-white);
--icon-donate-other-dark: var(--original-icon-donate-other-dark);
--icon-donate-other-white: var(--original-icon-donate-other-white);
--icon-send-dark: var(--original-icon-send-dark);
--icon-send-white: var(--original-icon-send-white);
--icon-relevance-dark: var(--original-icon-relevance-dark);
Expand Down Expand Up @@ -307,6 +319,30 @@ body .icon-feature-search-white,
body .icon-feature-search.icon-white {
background-image: var(--icon-feature-search-white);
}
body .icon-donate-paypal,
body .icon-donate-paypal-dark {
background-image: var(--icon-donate-paypal-dark);
}
body .icon-donate-paypal-white,
body .icon-donate-paypal.icon-white {
background-image: var(--icon-donate-paypal-white);
}
body .icon-donate-stripe,
body .icon-donate-stripe-dark {
background-image: var(--icon-donate-stripe-dark);
}
body .icon-donate-stripe-white,
body .icon-donate-stripe.icon-white {
background-image: var(--icon-donate-stripe-white);
}
body .icon-donate-other,
body .icon-donate-other-dark {
background-image: var(--icon-donate-other-dark);
}
body .icon-donate-other-white,
body .icon-donate-other.icon-white {
background-image: var(--icon-donate-other-white);
}
body .icon-send,
body .icon-send-dark {
background-image: var(--icon-send-dark);
Expand Down
2 changes: 2 additions & 0 deletions nextcloudappstore/core/static/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -926,11 +926,13 @@ address {
}

.interact-section h5,
.donate-section h5,
.support-section h5 {
margin-bottom: 20px;
}

.interact-section a, .interact-section button,
.donate-section a, .donate-section button,
.support-section a, .support-section button {
margin-bottom: 10px;
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions nextcloudappstore/core/templates/app/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ <h5>{% trans "Interact" %}</h5>
{% trans 'Ask questions or discuss' %}
</a>
</section>
{% if object.donations.all %}
<section class="donate-section">
<h5>{% trans 'Donate' %}</h5>
{% for donation in object.donations.all %}
<a rel="noreferrer noopener"
href="{{ donation.url }}"
class="btn btn-default btn-light">
<span class="icon icon-donate-{{ donation.type }}"></span>
{{ donation.title }}
</a>
{% endfor %}
</section>
{% endif %}
{% if object.is_enterprise_supported %}
<section class="support-section">
<h5>{% trans 'Need Enterprise Support?' %}</h5>
Expand Down
5 changes: 4 additions & 1 deletion nextcloudappstore/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
AppRegisterForm,
AppReleaseUploadForm,
)
from nextcloudappstore.core.models import App, AppRating, Category, Podcast
from nextcloudappstore.core.models import App, AppRating, Category, Donation, Podcast
from nextcloudappstore.core.serializers import AppRatingSerializer
from nextcloudappstore.core.versioning import pad_min_version

Expand Down Expand Up @@ -139,10 +139,13 @@ def get_context_data(self, **kwargs):
context["user_has_rated_app"] = True
except AppRating.DoesNotExist:
pass

context["donations"] = Donation.objects.filter(app=context["app"])
context["categories"] = Category.objects.prefetch_related("translations").all()
context["latest_releases_by_platform_v"] = self.object.latest_releases_by_platform_v()
context["is_integration"] = self.object.is_integration
context["is_outdated"] = self.object.is_outdated()

return context


Expand Down
Loading