Compare commits
No commits in common. "master" and "sustainer-payment" have entirely different histories.
master
...
sustainer-
56 changed files with 465 additions and 1219 deletions
|
@ -7,7 +7,7 @@ runs [sfconservancy.org](https://sfconservancy.org).
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
The canonical location for this repository is [on Conservancy’s
|
The canonical location for this repository is [on Conservancy’s
|
||||||
Forgejo instance](https://f.sfconservancy.org/Conservancy/website).
|
Kallithea instance](https://k.sfconservancy.org/website).
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
1
TODO.md
1
TODO.md
|
@ -1,6 +1,5 @@
|
||||||
# To-do
|
# To-do
|
||||||
|
|
||||||
* rate limiting to prevent abuse - especially on POST requests
|
|
||||||
* consider removing `events` and `worldmap` modules
|
* consider removing `events` and `worldmap` modules
|
||||||
* ask Denver about why so many license files
|
* ask Denver about why so many license files
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ set -e # Abort on failure
|
||||||
git push
|
git push
|
||||||
ssh debian@hickory.sfconservancy.org 'bash -s' << EOF
|
ssh debian@hickory.sfconservancy.org 'bash -s' << EOF
|
||||||
set -x # Show output
|
set -x # Show output
|
||||||
set -e # Abort on errors
|
set -e
|
||||||
|
|
||||||
cd /var/www/website
|
cd /var/www/website
|
||||||
sudo -u www-data git pull
|
sudo -u www-data git pull
|
||||||
sudo chown www-data:www-data .
|
sudo chown www-data:www-data .
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
this site</a>, but it may be more interesting to know that the site
|
this site</a>, but it may be more interesting to know that the site
|
||||||
is running on stock Debian 8 with Apache, Django, mod_python,
|
is running on stock Debian 8 with Apache, Django, mod_python,
|
||||||
and sqlite installed, and that the main sources for the site's code
|
and sqlite installed, and that the main sources for the site's code
|
||||||
itself are <a href="https://f.sfconservancy.org/Conservancy/website">available in
|
itself are <a href="https://k.sfconservancy.org/website">available in
|
||||||
a git repository</a>.</p>
|
a git repository</a>.</p>
|
||||||
|
|
||||||
<p>The documents on this website are
|
<p>The documents on this website are
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
a <a href="https://sfconservancy.org/docs/software-freedom-conservancy-v-vizio_2023-12-01_SFC-Motion-Summary-Adjudication.pdf">motion
|
a <a href="https://sfconservancy.org/docs/software-freedom-conservancy-v-vizio_2023-12-01_SFC-Motion-Summary-Adjudication.pdf">motion
|
||||||
for summary adjudication</a>.</p>
|
for summary adjudication</a>.</p>
|
||||||
|
|
||||||
<p>The case's expected trial date is mid-2025.</p>
|
<p>The case's expected trial date is later in 2024.</p>
|
||||||
|
|
||||||
<h3>Portions of Interest from the Docket in the Vizio Case</h3>
|
<h3>Portions of Interest from the Docket in the Vizio Case</h3>
|
||||||
|
|
||||||
|
@ -49,20 +49,10 @@ Original Complaint (2021-10-19)</li>
|
||||||
<li><a href="https://sfconservancy.org/docs/Order_Denying_Vizio_Motion_for_Summary_Judgement_12-29-23.pdf"><strong>Judge's
|
<li><a href="https://sfconservancy.org/docs/Order_Denying_Vizio_Motion_for_Summary_Judgement_12-29-23.pdf"><strong>Judge's
|
||||||
ruling denying Vizio's Motion for Summary Judgment</strong></a></li>
|
ruling denying Vizio's Motion for Summary Judgment</strong></a></li>
|
||||||
</ul></li>
|
</ul></li>
|
||||||
<li><a
|
|
||||||
href="https://usethesource.sfconservancy.org/tmp_vizio_docs/software-freedom-conservancy-v-vizio-first_amended_complaint-2024-01-10.pdf">SFC's
|
|
||||||
First Amended Complaint (2024-01-10)</li>
|
|
||||||
<li><h5>SFC's Motion for Summary Adjudication</h5>
|
<li><h5>SFC's Motion for Summary Adjudication</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://sfconservancy.org/docs/software-freedom-conservancy-v-vizio_2023-12-01_SFC-Motion-Summary-Adjudication.pdf">SFC's
|
<li><a href="https://sfconservancy.org/docs/software-freedom-conservancy-v-vizio_2023-12-01_SFC-Motion-Summary-Adjudication.pdf">SFC's
|
||||||
Motion for Summary Adjudication</a></li>
|
Motion for Summary Adjudication</a></li></ul></li>
|
||||||
<li><a href="https://usethesource.sfconservancy.org/tmp_vizio_docs/Vizio_response_to_motion_summary_adjudication.pdf">Vizio's
|
|
||||||
response to SFC's Motion for Summary Adjudication</a></li>
|
|
||||||
<li><a href="https://usethesource.sfconservancy.org/tmp_vizio_docs/SFC_motion_summary_adjudication_reply_brief.pdf">SFC's
|
|
||||||
reply to Vizio's response to SFC's Motion for Summary Adjudication</a></li>
|
|
||||||
<li><a href="https://usethesource.sfconservancy.org/tmp_vizio_docs/order_partially_granting_SFC_motion_summary_adjudication.pdf">Judge's
|
|
||||||
ruling partially granting SFC's Motion for Summary Adjudication</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>MEDIA CONTACT</h3>
|
<h3>MEDIA CONTACT</h3>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<p>Next, we compared the source code of the Linux Kernel 4.5.2 to the LLVM+Clang system, version 3.8.0. These two projects are each a large program that are not known to actively share code. There may be some very minimal similarity simply due to chance, but something much lower than the 3.68% found between Linux and FreeBSD's kernel.</p>
|
<p>Next, we compared the source code of the Linux Kernel 4.5.2 to the LLVM+Clang system, version 3.8.0. These two projects are each a large program that are not known to actively share code. There may be some very minimal similarity simply due to chance, but something much lower than the 3.68% found between Linux and FreeBSD's kernel.</p>
|
||||||
<p>Indeed, when the same test is run to compare Linux to the LLVM+Clang system, the "ratio of similarity" was 0.075%.</p>
|
<p>Indeed, when the same test is run to compare Linux to the LLVM+Clang system, the "ratio of similarity" was 0.075%.</p>
|
||||||
<h1 id="general-comparison-of-linux-kernel-to-vmware-sources">General Comparison of Linux Kernel to VMware sources</h1>
|
<h1 id="general-comparison-of-linux-kernel-to-vmware-sources">General Comparison of Linux Kernel to VMware sources</h1>
|
||||||
<p>With the baseline established, we now begin relevant comparisons. First, we compare the Linux kernel version 2.6.34 to the sources <a href="https://f.sfconservancy.org/Conservancy/vmkdrivers">released by VMware in their (partial) source release</a>. The "ratio of similarity" between Linux 2.6.34 and VMware's partial source release is 20.72%. There is little question that much of VMware's kernel has come from Linux.</p>
|
<p>With the baseline established, we now begin relevant comparisons. First, we compare the Linux kernel version 2.6.34 to the sources <a href="https://k.sfconservancy.org/vmkdrivers">released by VMware in their (partial) source release</a>. The "ratio of similarity" between Linux 2.6.34 and VMware's partial source release is 20.72%. There is little question that much of VMware's kernel has come from Linux.</p>
|
||||||
<h1 id="methodology-of-showing-hellwigs-contributions-in-vmware-esxi-5.5-sources">Methodology Of Showing Hellwig's Contributions in VMware ESXi 5.5 Sources</h1>
|
<h1 id="methodology-of-showing-hellwigs-contributions-in-vmware-esxi-5.5-sources">Methodology Of Showing Hellwig's Contributions in VMware ESXi 5.5 Sources</h1>
|
||||||
<p>The following describes a methodology to show Hellwig's contributions to Linux, and how they compare to code found in VMware ESXi 5.5.</p>
|
<p>The following describes a methodology to show Hellwig's contributions to Linux, and how they compare to code found in VMware ESXi 5.5.</p>
|
||||||
<h2 id="extracting-hellwigs-contributions-from-linux-historical-repository">Extracting Hellwig's Contributions From Linux Historical Repository</h2>
|
<h2 id="extracting-hellwigs-contributions-from-linux-historical-repository">Extracting Hellwig's Contributions From Linux Historical Repository</h2>
|
||||||
|
@ -31,7 +31,7 @@ $ ./extract-code-added-in-commits.plx --repository=`pwd`/linux-historical --outp
|
||||||
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-current
|
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-current
|
||||||
$ ./commit-id-list-matching-regex.plx `pwd`/linux-current/.git Hellwig '(Submitted\s+by|original\s+patch|patch\s+(from|by)|originally\s+(from|by)).*' > ./hellwig-current.ids
|
$ ./commit-id-list-matching-regex.plx `pwd`/linux-current/.git Hellwig '(Submitted\s+by|original\s+patch|patch\s+(from|by)|originally\s+(from|by)).*' > ./hellwig-current.ids
|
||||||
$ ./extract-code-added-in-commits.plx --progress --repository=`pwd`/linux-current --output-dir=`pwd`/hellwig-through-2.6.34 --fork-limit=14 --blame-opts=-M --blame-opts=-M --blame-opts=-C --blame-opts=-C --central-commit e40152ee1e1c7a63f4777791863215e3faa37a86 < hellwig-current.ids </code></pre>
|
$ ./extract-code-added-in-commits.plx --progress --repository=`pwd`/linux-current --output-dir=`pwd`/hellwig-through-2.6.34 --fork-limit=14 --blame-opts=-M --blame-opts=-M --blame-opts=-C --blame-opts=-C --central-commit e40152ee1e1c7a63f4777791863215e3faa37a86 < hellwig-current.ids </code></pre>
|
||||||
<p>Note: e40152ee1e1c7a63f4777791863215e3faa37a86 is the 2.6.34 version created by Linus Torvalds <script>
|
<p>Note: e40152ee1e1c7a63f4777791863215e3faa37a86 is the 2.6.34 version created by Linus Torvalds <script type="text/javascript">
|
||||||
<!--
|
<!--
|
||||||
h='linux-foundation.org';a='@';n='torvalds';e=n+a+h;
|
h='linux-foundation.org';a='@';n='torvalds';e=n+a+h;
|
||||||
document.write('<a h'+'ref'+'="ma'+'ilto'+':'+e+'">'+e+'<\/'+'a'+'>');
|
document.write('<a h'+'ref'+'="ma'+'ilto'+':'+e+'">'+e+'<\/'+'a'+'>');
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<li>you sign up as a Conservancy Sustainer or otherwise donate to Conservancy;</li>
|
<li>you sign up as a Conservancy Sustainer or otherwise donate to Conservancy;</li>
|
||||||
<li>you visit any Conservancy web site;</li>
|
<li>you visit any Conservancy web site;</li>
|
||||||
<li>you use one of Conservancy’s Mailman sites or lists, hosted at lists.sfconservancy.org or lists.copyleft.org;</li>
|
<li>you use one of Conservancy’s Mailman sites or lists, hosted at lists.sfconservancy.org or lists.copyleft.org;</li>
|
||||||
<li>you use one of Conservancy’s Forgejo sites or repositories, hosted at f.sfconservancy.org;</li>
|
<li>you use one of Conservancy’s Kallithea sites or repositories, hosted at k.sfconservancy.org or k.copyleft.org;</li>
|
||||||
<li>you use one of Conservancy’s Etherpad sites, hosted at pad.sfconservancy.org;</li>
|
<li>you use one of Conservancy’s Etherpad sites, hosted at pad.sfconservancy.org;</li>
|
||||||
<li>you use one of Conservancy’s wiki sites or partner wiki sites, hosted at npoacct.sfconservancy.org or copyleft.org;</li>
|
<li>you use one of Conservancy’s wiki sites or partner wiki sites, hosted at npoacct.sfconservancy.org or copyleft.org;</li>
|
||||||
<li>you use one of Conservancy’s project or partner project IRC channels, #npoacct and #copyleft on the Freenode IRC network;</li>
|
<li>you use one of Conservancy’s project or partner project IRC channels, #npoacct and #copyleft on the Freenode IRC network;</li>
|
||||||
|
|
|
@ -417,6 +417,20 @@ or safety.</p>
|
||||||
|
|
||||||
<p>MicroBlocks is a new programming language that runs right inside microcontroller boards such as the micro:bit, the NodeMCU and many Arduino boards. The MicroBlocks system allows for dynamic, parallel and interactive programming, but with the twist of letting your projects run autonomously inside the board without being tethered to a computer.</p>
|
<p>MicroBlocks is a new programming language that runs right inside microcontroller boards such as the micro:bit, the NodeMCU and many Arduino boards. The MicroBlocks system allows for dynamic, parallel and interactive programming, but with the twist of letting your projects run autonomously inside the board without being tethered to a computer.</p>
|
||||||
|
|
||||||
|
<h2><a href="https://www.northbaypython.org/">North Bay Python</a></h2>
|
||||||
|
|
||||||
|
<img class="project-logo" src="{% static 'img/projects/north-bay-python.png' %}" alt="" />
|
||||||
|
|
||||||
|
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||||
|
<input type="hidden" name="cmd" value="_s-xclick">
|
||||||
|
<input type="hidden" name="hosted_button_id" value="E96FCPFPZK25C">
|
||||||
|
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" name="submit" alt="Donate to North Bay Python via PayPal">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>North Bay Python is a community-organized conference in Petaluma,
|
||||||
|
California that brings together professionals, enthusiasts, and
|
||||||
|
students interested in the Python programming language.</p>
|
||||||
|
|
||||||
<h2><a href="http://www.opentripplanner.org/">OpenTripPlanner</a></h2>
|
<h2><a href="http://www.opentripplanner.org/">OpenTripPlanner</a></h2>
|
||||||
|
|
||||||
<img class="project-logo" src="{% static 'img/projects/opentripplanner.png' %}" alt="" />
|
<img class="project-logo" src="{% static 'img/projects/opentripplanner.png' %}" alt="" />
|
||||||
|
@ -514,6 +528,20 @@ seventy languages.</p>
|
||||||
KVM kernel module in Linux. When using KVM, QEMU can virtualize x86,
|
KVM kernel module in Linux. When using KVM, QEMU can virtualize x86,
|
||||||
server and embedded PowerPC, and S390 guests.</p>
|
server and embedded PowerPC, and S390 guests.</p>
|
||||||
|
|
||||||
|
<h2><a href="https://racket-lang.org">Racket</a></h2>
|
||||||
|
|
||||||
|
<img class="project-logo" src="{% static 'img/projects/racket.svg' %}" alt="" />
|
||||||
|
|
||||||
|
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||||
|
<input type="hidden" name="cmd" value="_s-xclick">
|
||||||
|
<input type="hidden" name="hosted_button_id" value="URMNGBCTB96G2">
|
||||||
|
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" name="submit" alt="Donate to Racket via PayPal">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>Racket is a general-purpose programming language as well as the world’s
|
||||||
|
first ecosystem for language-oriented programming. Make your dream language,
|
||||||
|
or use one of the dozens already available.</p>
|
||||||
|
|
||||||
<h2><a href="https://reproducible-builds.org">Reproducible Builds</a></h2>
|
<h2><a href="https://reproducible-builds.org">Reproducible Builds</a></h2>
|
||||||
|
|
||||||
<img class="project-logo" src="{% static 'img/projects/2018-10_Reproducible-Builds.svg' %}" alt="" />
|
<img class="project-logo" src="{% static 'img/projects/2018-10_Reproducible-Builds.svg' %}" alt="" />
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
<li><a href="conservancy-travel-policy.html">Travel and reimbursable expense policy</a></li>
|
<li><a href="conservancy-travel-policy.html">Travel and reimbursable expense policy</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>For more background about the policies, including licensing and change requests, please refer to <a href="https://f.sfconservancy.org/Conservancy/policies">their source code in Git</a>.</p>
|
<p>For more background about the policies, including licensing and change requests, please refer to <a href="https://k.sfconservancy.org/policies">their source code in Git</a>.</p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{% block category %}supporter{% endblock %}
|
{% block category %}supporter{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<script src="{% static 'js/supporter-page.js' %}" defer></script>
|
<script type="text/javascript" src="{% static 'js/\supporter-page.js' %}" defer></script>
|
||||||
<link href="{% static 'css/forms.css' %}" rel="stylesheet" type="text/css"/>
|
<link href="{% static 'css/forms.css' %}" rel="stylesheet" type="text/css"/>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
from captcha.fields import CaptchaField
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .models import CommunityTrackProposal
|
from .models import CommunityTrackProposal
|
||||||
|
|
||||||
|
|
||||||
class CommunityTrackProposalForm(forms.ModelForm):
|
class CommunityTrackProposalForm(forms.ModelForm):
|
||||||
captcha = CaptchaField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CommunityTrackProposal
|
model = CommunityTrackProposal
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
<div class="mw8 center ph2 ph3">
|
<div class="mw8 center ph2 ph3">
|
||||||
<h1><abbr title="Free and Open Source Software Yearly">FOSSY</abbr>: Propose a Commmunity Track!</h1>
|
<h1><abbr title="Free and Open Source Software Yearly">FOSSY</abbr>: Propose a Commmunity Track!</h1>
|
||||||
<div class="mw7 mb5">
|
<div class="mw7 mb5">
|
||||||
<p>SFC will be hosting a community oriented conference this coming summer July 31st - August 3rd, 2025 in Portland, Oregon in the United States. Focused on the creation and impact of free and open source software, uplifting contributors of all experience. We plan to have 8 tracks of talks over 4 days and to dedicate a substantial portion of these track to community run tracks, similar to the DevRooms at FOSDEM or the miniconfs at LinuxConfAU. We'd like to invite you to run a track based on a topic you're passionate about. If selected, you will be responsible for inviting speakers, selecting talks and organising the schedule for your track. If that sounds good to you, please fill in the form to tell us more about your idea. If you have any questions please don't hesitate to email us at <a href="mailto:conference@sfconservancy.org">conference@sfconservancy.org</a>.</p>
|
<p>SFC will be hosting a community oriented conference this coming summer August 1-4th, 2024 in Portland, Oregon in the United States. Focused on the creation and impact of free and open source software, uplifting contributors of all experience. We plan to have 8 tracks of talks over 4 days and to dedicate a substantial portion of these track to community run tracks, similar to the DevRooms at FOSDEM or the miniconfs at LinuxConfAU. We'd like to invite you to run a track based on a topic you're passionate about. If selected, you will be responsible for inviting speakers, selecting talks and organising the schedule for your track. If that sounds good to you, please fill in the form to tell us more about your idea. If you have any questions please don't hesitate to email us at <a href="mailto:conference@sfconservancy.org">conference@sfconservancy.org</a>.</p>
|
||||||
|
|
||||||
<p><strong>Please understand that organizing a track is a signficant amount of work</strong>, and while we'll be so grateful for your contributions, depending on sponsor sign ups we are unlikely to be able to pay stipends or fund travel for speakers or organizers of your track (please let us know if travel is burdensome for you as an organizer). Given the high work load of organizing a conference track, we expect at least two people to be responsible (see primary and secondary proposer's below). Feel free to include more later, but we need at least two people for the proposal.</p>
|
<p><strong>Please understand that organizing a track is a signficant amount of work</strong>, and while we'll be so grateful for your contributions, depending on sponsor sign ups we are unlikely to be able to pay stipends or fund travel for speakers or organizers of your track (please let us know if travel is burdensome for you as an organizer). Given the high work load of organizing a conference track, we expect at least two people to be responsible (see primary and secondary proposer's below). Feel free to include more later, but we need at least two people for the proposal.</p>
|
||||||
|
|
||||||
<p>Please fill out the questions below to apply. We want the conference to appeal to a wide audience, so are looking for tracks ranging from specific technical topics and people-focused themes, to the collaborative future of free software. We'd like to see a diversity of both niche and general topics that allow people from different backgrounds to participate.</p>
|
<p>Please fill out the questions below to apply. We want the conference to appeal to a wide audience, so are looking for tracks ranging from specific technical topics and people-focused themes, to the collaborative future of free software. We'd like to see a diversity of both niche and general topics that allow people from different backgrounds to participate.</p>
|
||||||
|
|
||||||
<p><strong>The deadline for submission is Sunday February 16th 2025.</strong></p>
|
<p><strong>The deadline for submission is Thursday April 25th 2024.</strong></p>
|
||||||
|
|
||||||
<form action="." method="post" class="mw7">
|
<form action="." method="post" class="mw7">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
|
@ -63,12 +63,7 @@ LOGGING = {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
}
|
||||||
'conservancy.supporters': {
|
|
||||||
'handlers': ['console'],
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
'root': {
|
'root': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
|
@ -98,7 +93,6 @@ INSTALLED_APPS = [
|
||||||
'conservancy.fossy',
|
'conservancy.fossy',
|
||||||
'conservancy.podjango',
|
'conservancy.podjango',
|
||||||
'conservancy.usethesource.apps.UseTheSourceConfig',
|
'conservancy.usethesource.apps.UseTheSourceConfig',
|
||||||
'captcha',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
@ -129,20 +123,12 @@ TIME_ZONE = 'America/New_York'
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
USE_TZ = False
|
USE_TZ = False
|
||||||
|
|
||||||
STORAGES = {
|
|
||||||
'default': {
|
|
||||||
'BACKEND': 'django.core.files.storage.FileSystemStorage',
|
|
||||||
},
|
|
||||||
'staticfiles': {
|
|
||||||
'BACKEND': 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = BASE_DIR.parent / 'collected_static'
|
STATIC_ROOT = BASE_DIR.parent / 'collected_static'
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
BASE_DIR / 'static',
|
BASE_DIR / 'static',
|
||||||
]
|
]
|
||||||
|
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||||
|
|
||||||
MEDIA_ROOT = BASE_DIR.parent / 'media'
|
MEDIA_ROOT = BASE_DIR.parent / 'media'
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = '/media/'
|
||||||
|
@ -159,4 +145,4 @@ USETHESOURCE = {
|
||||||
'LIST_RECIPIENT': 'ccs-review@lists.sfconservancy.org',
|
'LIST_RECIPIENT': 'ccs-review@lists.sfconservancy.org',
|
||||||
}
|
}
|
||||||
|
|
||||||
SITE_FUNDGOAL = 'cy2024-end-year-match'
|
SITE_FUNDGOAL = 'cy2023-end-year-match'
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from .base import * # NOQA
|
from .base import * # NOQA
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
@ -15,6 +13,3 @@ DATABASES = {
|
||||||
SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
STRIPE_API_KEY = os.getenv('STRIPE_API_KEY', '')
|
|
||||||
STRIPE_ENDPOINT_SECRET = os.getenv('STRIPE_ENDPOINT_SECRET', '')
|
|
||||||
|
|
|
@ -37,10 +37,3 @@ def get_secret(secrets, setting):
|
||||||
SECRET_KEY = get_secret(secrets, 'SECRET_KEY')
|
SECRET_KEY = get_secret(secrets, 'SECRET_KEY')
|
||||||
|
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
STRIPE_API_KEY = get_secret(secrets, 'STRIPE_API_KEY')
|
|
||||||
STRIPE_ENDPOINT_SECRET = get_secret(secrets, 'STRIPE_ENDPOINT_SECRET')
|
|
||||||
|
|
||||||
CAPTCHA_FLITE_PATH = '/usr/bin/flite'
|
|
||||||
CAPTCHA_SOX_PATH = '/usr/bin/sox'
|
|
||||||
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
|
|
||||||
|
|
|
@ -76,6 +76,12 @@ form[action$="#fixme"]:before {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
z-index: 3;
|
||||||
|
border-color: #86b7fe;
|
||||||
|
box-shadow: 0 0 0 .25rem rgb(236, 99, 67, .5);
|
||||||
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 265 KiB |
Binary file not shown.
Before Width: | Height: | Size: 147 KiB |
Binary file not shown.
Before Width: | Height: | Size: 272 KiB After Width: | Height: | Size: 292 KiB |
Binary file not shown.
Before Width: | Height: | Size: 570 KiB |
|
@ -1,6 +1,6 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Supporter, SustainerOrder, SustainerPayment
|
from .models import Supporter, SustainerOrder
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Supporter)
|
@admin.register(Supporter)
|
||||||
|
@ -8,39 +8,11 @@ class SupporterAdmin(admin.ModelAdmin):
|
||||||
list_display = ('display_name', 'display_until_date')
|
list_display = ('display_name', 'display_until_date')
|
||||||
|
|
||||||
|
|
||||||
class SustainerPaymentInline(admin.TabularInline):
|
|
||||||
model = SustainerPayment
|
|
||||||
fields = [
|
|
||||||
'paid_time',
|
|
||||||
'amount',
|
|
||||||
'stripe_payment_intent_ref',
|
|
||||||
'stripe_invoice_ref',
|
|
||||||
]
|
|
||||||
can_delete = False
|
|
||||||
readonly_fields = [
|
|
||||||
'paid_time',
|
|
||||||
'amount',
|
|
||||||
'stripe_payment_intent_ref',
|
|
||||||
'stripe_invoice_ref',
|
|
||||||
]
|
|
||||||
|
|
||||||
def has_add_permission(self, request, obj=None):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_change_permission(self, request, obj=None):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SustainerOrder)
|
@admin.register(SustainerOrder)
|
||||||
class SustainerOrderAdmin(admin.ModelAdmin):
|
class SustainerOrderAdmin(admin.ModelAdmin):
|
||||||
fields = [
|
fields = [
|
||||||
'created_time',
|
'created_time',
|
||||||
'paid_time',
|
'paid_time',
|
||||||
'payment_method',
|
|
||||||
'stripe_customer_ref',
|
|
||||||
'stripe_subscription_ref',
|
|
||||||
'recurring',
|
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'amount',
|
'amount',
|
||||||
|
@ -53,21 +25,7 @@ class SustainerOrderAdmin(admin.ModelAdmin):
|
||||||
'zip_code',
|
'zip_code',
|
||||||
'country',
|
'country',
|
||||||
]
|
]
|
||||||
inlines = [SustainerPaymentInline]
|
|
||||||
readonly_fields = ['created_time', 'paid_time', 'payment_method', 'stripe_customer_ref', 'stripe_subscription_ref', 'recurring']
|
|
||||||
list_display = ['created_time', 'name', 'email', 'amount', 'recurring', 'paid_time']
|
|
||||||
list_filter = ['paid_time']
|
|
||||||
|
|
||||||
|
readonly_fields = ['created_time', 'paid_time']
|
||||||
@admin.register(SustainerPayment)
|
list_display = ['created_time', 'name', 'email', 'amount', 'paid']
|
||||||
class SustainerPaymentAdmin(admin.ModelAdmin):
|
|
||||||
fields = [
|
|
||||||
'order',
|
|
||||||
'paid_time',
|
|
||||||
'amount',
|
|
||||||
'stripe_invoice_ref',
|
|
||||||
'stripe_payment_intent_ref',
|
|
||||||
]
|
|
||||||
readonly_fields = ['order', 'paid_time', 'amount', 'stripe_invoice_ref', 'stripe_payment_intent_ref']
|
|
||||||
list_display = ['order', 'paid_time', 'amount']
|
|
||||||
list_filter = ['paid_time']
|
list_filter = ['paid_time']
|
||||||
|
|
|
@ -1,65 +1,16 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .models import SustainerOrder
|
from .models import SustainerOrder
|
||||||
|
|
||||||
|
|
||||||
class SustainerFormRenderer(forms.renderers.DjangoTemplates):
|
|
||||||
# Customised layout with labels on own row
|
|
||||||
field_template_name = 'supporters/field.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ButtonRadioSelect(forms.widgets.RadioSelect):
|
|
||||||
"""Radio button styled like a button."""
|
|
||||||
|
|
||||||
# Extra <span> wrappers to support CSS
|
|
||||||
option_template_name = 'supporters/buttonradio_option.html'
|
|
||||||
use_fieldset = False
|
|
||||||
|
|
||||||
class Media:
|
|
||||||
css = {
|
|
||||||
'all': ['css/buttonradio.css'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.attrs['class'] = 'button-select'
|
|
||||||
|
|
||||||
|
|
||||||
class SustainerForm(forms.ModelForm):
|
class SustainerForm(forms.ModelForm):
|
||||||
"""Sustainer sign-up
|
amount_monthly = forms.IntegerField(initial=12, required=False)
|
||||||
|
|
||||||
The logic for this form is somewhat spread between this Django form and the Django
|
|
||||||
template and Alpine JS code in the template.
|
|
||||||
|
|
||||||
Having to define some of the the Alpine JS attributes here in the form and some in
|
|
||||||
the template feels awkward, and I wish there was a better way. Django Crispy Forms
|
|
||||||
is typically a good option, but I really wanted to see if the new Django 5 form
|
|
||||||
improvements could beat that (eg. ".as_field_group"). They certainly help, but put
|
|
||||||
several levels of abstraction between you and the HTML (eg. renderers) and spread
|
|
||||||
your HTML across various template and code files. While I appreciate not having to
|
|
||||||
write code to render checked and unchecked boxes, designing attractive interactive
|
|
||||||
forms shouldn't be this complicated.
|
|
||||||
|
|
||||||
Alpine JS has its own trade-offs here. There's nearly no JavaScript as such, but the
|
|
||||||
"x-.." attributes are meaningless until you read the Alpine docs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# To pre-fill the price option buttons in the case of server-side validation errors.
|
|
||||||
amount_option = forms.CharField(required=False)
|
|
||||||
|
|
||||||
template_name = 'supporters/sustainer_form.html'
|
|
||||||
|
|
||||||
MONTH_OPTIONS = [12, 23, 45, 87]
|
|
||||||
YEAR_OPTIONS = [128, 256, 512, 1024]
|
|
||||||
MONTH_MINIMUM = 10
|
|
||||||
YEAR_MINIMUM = 120
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SustainerOrder
|
model = SustainerOrder
|
||||||
fields = [
|
fields = [
|
||||||
'recurring',
|
|
||||||
'amount',
|
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
|
'amount',
|
||||||
'acknowledge_publicly',
|
'acknowledge_publicly',
|
||||||
'add_to_mailing_list',
|
'add_to_mailing_list',
|
||||||
'tshirt_size',
|
'tshirt_size',
|
||||||
|
@ -69,49 +20,10 @@ class SustainerForm(forms.ModelForm):
|
||||||
'zip_code',
|
'zip_code',
|
||||||
'country',
|
'country',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'recurring': ButtonRadioSelect(
|
|
||||||
attrs={
|
|
||||||
'x-model': 'recurring',
|
|
||||||
# Reset the amount field and option when changing monthly/annually.
|
|
||||||
'x-on:change': 'amount = ""; amount_option = null',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
'amount': forms.widgets.NumberInput(
|
|
||||||
# Retaining default widget, just neater to add many attrs here.
|
|
||||||
attrs={
|
|
||||||
# So we can update the amount field from the amount_option selected.
|
|
||||||
'x-model': 'amount',
|
|
||||||
'x-bind:min': 'amount_minimum',
|
|
||||||
'onblur': 'this.reportValidity()',
|
|
||||||
'style': 'width: 5rem',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['amount'].widget.attrs['style'] = 'width: 5rem'
|
||||||
self.renderer = SustainerFormRenderer()
|
self.fields['amount'].initial = 128
|
||||||
|
self.fields['amount_monthly'].widget.attrs['style'] = 'width: 5rem'
|
||||||
self.fields['recurring'].label = ''
|
|
||||||
self.fields['amount'].initial = self.YEAR_OPTIONS[0]
|
|
||||||
self.fields['tshirt_size'].widget.attrs['x-model'] = 'tshirt_size'
|
self.fields['tshirt_size'].widget.attrs['x-model'] = 'tshirt_size'
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
super().clean()
|
|
||||||
recurring = self.cleaned_data.get('recurring', '')
|
|
||||||
amount = self.cleaned_data.get('amount', 0)
|
|
||||||
minimum = self.MONTH_MINIMUM if recurring == 'month' else self.YEAR_MINIMUM
|
|
||||||
if amount < minimum:
|
|
||||||
self.add_error('', f'${minimum:d} is a minimum for Conservancy Sustainers.')
|
|
||||||
tshirt_size = self.cleaned_data.get('tshirt_size')
|
|
||||||
address_provided = all(
|
|
||||||
[
|
|
||||||
self.cleaned_data.get('street'),
|
|
||||||
self.cleaned_data.get('city'),
|
|
||||||
self.cleaned_data.get('country'),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if tshirt_size and not address_provided:
|
|
||||||
self.add_error('street', 'No address provided')
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
from django.core.mail import EmailMessage
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
|
|
||||||
def make_stripe_email(order) -> EmailMessage:
|
|
||||||
subject = 'Thanks for being a sustainer!'
|
|
||||||
email_body = render_to_string(
|
|
||||||
'supporters/mail/sustainer_thanks.txt',
|
|
||||||
{'order': order},
|
|
||||||
).strip()
|
|
||||||
message = EmailMessage(
|
|
||||||
subject,
|
|
||||||
email_body,
|
|
||||||
'Software Freedom Conservancy <sustainers@sfconservancy.org>',
|
|
||||||
[order.email],
|
|
||||||
)
|
|
||||||
return message
|
|
|
@ -1,33 +0,0 @@
|
||||||
import csv
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from ...models import SustainerPayment
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Closes the specified poll for voting"
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
payments = SustainerPayment.objects.select_related('order').order_by('paid_time')
|
|
||||||
columns = ['order_time', 'payment_time', 'name', 'email', 'amount', 'transaction_id', 'public_ack', 'shirt_size', 'join_list', 'street', 'city', 'state', 'zip_code', 'country']
|
|
||||||
writer = csv.writer(sys.stdout)
|
|
||||||
writer.writerow(columns)
|
|
||||||
for payment in payments:
|
|
||||||
order = payment.order
|
|
||||||
writer.writerow([
|
|
||||||
order.created_time,
|
|
||||||
payment.paid_time,
|
|
||||||
order.name,
|
|
||||||
order.email,
|
|
||||||
payment.amount,
|
|
||||||
payment.stripe_payment_intent_ref,
|
|
||||||
order.acknowledge_publicly,
|
|
||||||
repr(order.tshirt_size if order.tshirt_size else ''),
|
|
||||||
order.add_to_mailing_list,
|
|
||||||
order.street,
|
|
||||||
order.city,
|
|
||||||
order.state,
|
|
||||||
order.zip_code,
|
|
||||||
order.country,
|
|
||||||
])
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 4.2.11 on 2024-10-08 09:44
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('supporters', '0003_remove_sustainerorder_monthly_recurring_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='payment_id',
|
|
||||||
field=models.CharField(blank=True, max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='payment_method',
|
|
||||||
field=models.CharField(default='Stripe', max_length=10),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,71 +0,0 @@
|
||||||
# Generated by Django 5.1.2 on 2024-10-22 04:16
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('supporters', '0004_sustainerorder_payment_id_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='amount',
|
|
||||||
field=models.PositiveIntegerField(),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='recurring',
|
|
||||||
field=models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[('', 'Once'), ('month', 'Monthly'), ('year', 'Annually')],
|
|
||||||
default='',
|
|
||||||
max_length=10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='tshirt_size',
|
|
||||||
field=models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[
|
|
||||||
('', [('', 'None')]),
|
|
||||||
(
|
|
||||||
"Men's",
|
|
||||||
[
|
|
||||||
("Men's S", "Men's S"),
|
|
||||||
("Men's M", "Men's M"),
|
|
||||||
("Men's L", "Men's L"),
|
|
||||||
("Men's XL", "Men's XL"),
|
|
||||||
("Men's 2XL", "Men's 2XL"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Standard women's",
|
|
||||||
[
|
|
||||||
("Standard women's S", "Standard women's S"),
|
|
||||||
("Standard women's M", "Standard women's M"),
|
|
||||||
("Standard women's L", "Standard women's L"),
|
|
||||||
("Standard women's XL", "Standard women's XL"),
|
|
||||||
("Standard women's 2XL", "Standard women's 2XL"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Fitted women's",
|
|
||||||
[
|
|
||||||
("Fitted women's S", "Fitted women's S"),
|
|
||||||
("Fitted women's M", "Fitted women's M"),
|
|
||||||
("Fitted women's L", "Fitted women's L"),
|
|
||||||
("Fitted women's XL", "Fitted women's XL"),
|
|
||||||
("Fitted women's 2XL", "Fitted women's 2XL"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
default='',
|
|
||||||
max_length=50,
|
|
||||||
verbose_name='T-shirt size',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,75 +0,0 @@
|
||||||
# Generated by Django 5.1.2 on 2024-11-15 02:56
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('supporters', '0005_alter_sustainerorder_amount_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='payment_id',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='stripe_checkout_session_data',
|
|
||||||
field=models.JSONField(null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='stripe_customer_ref',
|
|
||||||
field=models.CharField(max_length=50, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='stripe_initial_payment_intent_ref',
|
|
||||||
field=models.CharField(max_length=50, null=True, unique=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='stripe_subscription_ref',
|
|
||||||
field=models.CharField(max_length=50, null=True, unique=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sustainerorder',
|
|
||||||
name='amount',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=7),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SustainerPayment',
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
'id',
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name='ID',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
('paid_time', models.DateTimeField(auto_now_add=True)),
|
|
||||||
(
|
|
||||||
'stripe_invoice_ref',
|
|
||||||
models.CharField(max_length=50, null=True, unique=True),
|
|
||||||
),
|
|
||||||
('amount', models.DecimalField(decimal_places=2, max_digits=7)),
|
|
||||||
(
|
|
||||||
'stripe_payment_intent_ref',
|
|
||||||
models.CharField(max_length=50, null=True, unique=True),
|
|
||||||
),
|
|
||||||
('stripe_invoice_data', models.JSONField(null=True)),
|
|
||||||
(
|
|
||||||
'order',
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to='supporters.sustainerorder',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.core import validators
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,14 +6,11 @@ class Supporter(models.Model):
|
||||||
"""Conservancy Supporter listing"""
|
"""Conservancy Supporter listing"""
|
||||||
|
|
||||||
display_name = models.CharField(max_length=200, blank=False)
|
display_name = models.CharField(max_length=200, blank=False)
|
||||||
display_until_date = models.DateTimeField(
|
display_until_date = models.DateTimeField("date until which this supporter name is displayed")
|
||||||
"date until which this supporter name is displayed"
|
|
||||||
)
|
|
||||||
ledger_entity_id = models.CharField(max_length=200, blank=False)
|
ledger_entity_id = models.CharField(max_length=200, blank=False)
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
return "TESTING"
|
return "TESTING"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.display_name
|
return self.display_name
|
||||||
|
|
||||||
|
@ -22,14 +20,14 @@ class Supporter(models.Model):
|
||||||
|
|
||||||
class SustainerOrder(models.Model):
|
class SustainerOrder(models.Model):
|
||||||
RENEW_CHOICES = [
|
RENEW_CHOICES = [
|
||||||
('', 'Once'),
|
('', 'None'),
|
||||||
('month', 'Monthly'),
|
('month', 'Monthly'),
|
||||||
('year', 'Annually'),
|
('year', 'Annual'),
|
||||||
]
|
]
|
||||||
TSHIRT_CHOICES = [
|
TSHIRT_CHOICES = [
|
||||||
(
|
(
|
||||||
'',
|
'',
|
||||||
(("", "None"),),
|
(("None", "None"),),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Men's",
|
"Men's",
|
||||||
|
@ -64,23 +62,17 @@ class SustainerOrder(models.Model):
|
||||||
]
|
]
|
||||||
|
|
||||||
created_time = models.DateTimeField(auto_now_add=True)
|
created_time = models.DateTimeField(auto_now_add=True)
|
||||||
stripe_customer_ref = models.CharField(max_length=50, null=True)
|
|
||||||
stripe_subscription_ref = models.CharField(max_length=50, null=True, unique=True)
|
|
||||||
stripe_initial_payment_intent_ref = models.CharField(max_length=50, null=True, unique=True)
|
|
||||||
stripe_checkout_session_data = models.JSONField(null=True)
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
amount = models.DecimalField(max_digits=7, decimal_places=2)
|
amount = models.IntegerField(
|
||||||
recurring = models.CharField(
|
validators=[
|
||||||
max_length=10, choices=RENEW_CHOICES, blank=True, default=''
|
validators.MinValueValidator(100),
|
||||||
)
|
])
|
||||||
payment_method = models.CharField(max_length=10, default='Stripe')
|
recurring = models.CharField(max_length=10)
|
||||||
paid_time = models.DateTimeField(null=True, blank=True)
|
paid_time = models.DateTimeField(null=True, blank=True)
|
||||||
acknowledge_publicly = models.BooleanField(default=True)
|
acknowledge_publicly = models.BooleanField(default=True)
|
||||||
add_to_mailing_list = models.BooleanField(default=True)
|
add_to_mailing_list = models.BooleanField(default=True)
|
||||||
tshirt_size = models.CharField(
|
tshirt_size = models.CharField(max_length=50, choices=TSHIRT_CHOICES)
|
||||||
'T-shirt size', max_length=50, choices=TSHIRT_CHOICES, blank=True, default=''
|
|
||||||
)
|
|
||||||
street = models.CharField(max_length=255, blank=True)
|
street = models.CharField(max_length=255, blank=True)
|
||||||
city = models.CharField(max_length=255, blank=True)
|
city = models.CharField(max_length=255, blank=True)
|
||||||
state = models.CharField(max_length=255, blank=True)
|
state = models.CharField(max_length=255, blank=True)
|
||||||
|
@ -92,12 +84,3 @@ class SustainerOrder(models.Model):
|
||||||
|
|
||||||
def paid(self):
|
def paid(self):
|
||||||
return self.paid_time is not None
|
return self.paid_time is not None
|
||||||
|
|
||||||
|
|
||||||
class SustainerPayment(models.Model):
|
|
||||||
order = models.ForeignKey(SustainerOrder, on_delete=models.CASCADE)
|
|
||||||
paid_time = models.DateTimeField(auto_now_add=True)
|
|
||||||
stripe_invoice_ref = models.CharField(max_length=50, null=True, unique=True)
|
|
||||||
amount = models.DecimalField(max_digits=7, decimal_places=2)
|
|
||||||
stripe_payment_intent_ref = models.CharField(max_length=50, null=True, unique=True)
|
|
||||||
stripe_invoice_data = models.JSONField(null=True)
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
.button-select {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.5rem;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-select label > span {
|
|
||||||
text-align: center;
|
|
||||||
display: inline-block;
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
width: 100%;
|
|
||||||
background: #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-select label input {
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wish we could use :has reliably. */
|
|
||||||
.button-select label input:focus + span {
|
|
||||||
border-color: #eee;
|
|
||||||
outline: 2px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-select label input:checked + span {
|
|
||||||
color: white;
|
|
||||||
background: #666;
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
{# Custom <span> wrapper around the label to enable radio fields to be styled like buttons. #}
|
|
||||||
{% if widget.wrap_label %}<label onclick="click()"{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} <span>{{ widget.label }}</span></label>{% endif %}
|
|
|
@ -1,11 +0,0 @@
|
||||||
{# Labels on a separate line, custom help text layout #}
|
|
||||||
{% if field.use_fieldset %}
|
|
||||||
<fieldset{% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}>
|
|
||||||
{% if field.label %}{{ field.legend_tag }}{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if field.label %}{{ field.label_tag }}{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{{ field.errors }}
|
|
||||||
<div class="mt1">{{ field }}</div>
|
|
||||||
<div class="f7 black-60 mt1">{{ field.help_text }}</div>
|
|
||||||
{% if field.use_fieldset %}</fieldset>{% endif %}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{% autoescape off %}Hi {{ order.name }},
|
|
||||||
|
|
||||||
Thanks so much for being a sustainer! Your support is what makes our work possible.
|
|
||||||
|
|
||||||
Order: #{{ order.id }}
|
|
||||||
Payment: ${{ order.amount }}{% if order.recurring %} {{ order.get_recurring_display }}{% endif %}
|
|
||||||
Acknowledge me on the list of sustainers: {{ order.acknowledge_publicly|yesno }}
|
|
||||||
Add me to the announcements email list: {{ order.add_to_mailing_list|yesno }}
|
|
||||||
T-shirt: {{ order.get_tshirt_size_display }}{% if order.tshirt_size %}
|
|
||||||
Postal address:
|
|
||||||
{{ order.street }}
|
|
||||||
{{ order.city }} {{ order.state }} {{ order.zip_code }}
|
|
||||||
{{ order.country }}{% endif %}
|
|
||||||
{% if order.recurring == 'month' and order.tshirt_size %}
|
|
||||||
Please note that you may not receive the T-shirt until you've paid at least $60.{% endif %}
|
|
||||||
|
|
||||||
Kind regards,
|
|
||||||
Software Freedom Conservancy{% endautoescape %}
|
|
|
@ -4,38 +4,5 @@
|
||||||
{% block category %}sustainer{% endblock %}
|
{% block category %}sustainer{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="lh-title mt4">Thanks!</h1>
|
<h1 class="lh-title tc mt4 mb4">Thanks!</h1>
|
||||||
|
|
||||||
<p>Thank you for being a Sustainer of Software Freedom Conservancy!</p>
|
|
||||||
|
|
||||||
<!-- <p>Are you at LinuxFest Northwest right now? If so, you are now eligible to
|
|
||||||
attend a special Sustainer-only dinner and drinks on Saturday 23 April 2016
|
|
||||||
at 6:30PM, but space is limited! please RSVP
|
|
||||||
by <a href="mailto:rsvp-lfnw@sfconservancy.org">email to
|
|
||||||
<rsvp-lfnw@sfconservancy.org></a>. Let us know any dietary
|
|
||||||
restrictions in your email. We'll email back with details of where the
|
|
||||||
event is.</p>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<p>As a Conservancy Sustainer, you'll also be eligible for future special
|
|
||||||
benefits. We may contact you directly by email later to tell you about
|
|
||||||
special Sustainer-only benefits in the coming year.</p>
|
|
||||||
|
|
||||||
<p>Meanwhile, please spread the word about supporting Conservancy with
|
|
||||||
a “Sustainer Badge” on your website, social media, or
|
|
||||||
other locations where people view information about you:</p>
|
|
||||||
|
|
||||||
<p><a href="https://sfconservancy.org/sustainer/"><img src="https://sfconservancy.org/static/img/supporter-badge.png" width="194" height="90" alt="Become a Conservancy Sustainer!" border="0"/></a></p>
|
|
||||||
|
|
||||||
<p><strong>Copy and paste this HTML for the image above:</strong></p>
|
|
||||||
<p><textarea rows="2"
|
|
||||||
cols="65">
|
|
||||||
<a href="https://sfconservancy.org/sustainer/"><img src="https://sfconservancy.org/static/img/supporter-badge.png" width="194" height="90" alt="Become a Conservancy Sustainer!" border="0"/></a>
|
|
||||||
</textarea></p>
|
|
||||||
|
|
||||||
<p class="mb5">Also, please enjoy these “Sustainer Cards”, which you print out and
|
|
||||||
carry with you. You've earned it! The cards are available in two different
|
|
||||||
styles: <a href="/static/img/supporter-card-1.12b4668a6b78.svg">Style 1
|
|
||||||
(SVG)</a>, <a href="/static/img/supporter-card-2.9c9e76d445e5.svg">Style 2
|
|
||||||
(SVG)</a>.</p>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
{% extends "base_conservancy.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% block subtitle %}Support Conservancy - {% endblock %}
|
|
||||||
{% block category %}sustainer{% endblock %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<script src="{% static 'js/supporter-page.js' %}" defer></script>
|
|
||||||
<link href="{% static 'css/forms.css' %}" rel="stylesheet" type="text/css"/>
|
|
||||||
<style>
|
|
||||||
.hidden { display: none; }
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1 class="lh-title tc mt4">Become a Sustainer by PayPal</h1>
|
|
||||||
|
|
||||||
<div class="content-with-donate-sidebar" id="formStart">
|
|
||||||
|
|
||||||
{% if partial_amount > 0 %}
|
|
||||||
{% include "supporters/form_partial.html" with form_id="annual" min_amt=minimum_amount partial_amt=partial_amount article="an" only %}
|
|
||||||
{% else %}
|
|
||||||
<div class="supporter-type-selector">
|
|
||||||
<a id="annualSelector" href="#annual">Annual</a>
|
|
||||||
| <a id="monthlySelector" href="#monthly">Monthly</a>
|
|
||||||
| <a id="renewalSelector" href="#renewal">Annual Renew</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include "supporters/form_partial.html" with form_id="annual" min_amt=120 default_amt=128 article="an" only %}
|
|
||||||
|
|
||||||
{% include "supporters/form_partial.html" with form_id="monthly" min_amt=10 default_amt=12 only %}
|
|
||||||
|
|
||||||
<a name="renew" class="hidden"></a>
|
|
||||||
{% include "supporters/form_partial.html" with form_id="renewal" min_amt=120 default_amt=128 verb="renew" article="an" supptype="annual" only %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<span id="form-correction-needed" class="form-error">Please ensure all form data above is correct.</span>
|
|
||||||
{% endblock %}
|
|
|
@ -5,165 +5,40 @@
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% include "opengraph_partial.html" with url="/sustainer/" title="Support Conservancy!" description="Software freedom is critical to many of today’s most pressing social issues, but it’s only effective when FOSS is for everyone. Support Conservancy today to help make that happen!" %}
|
|
||||||
{% include "opengraph_urllist_partial.html" with property='image' urls='' fallback='/static/img/conservancy-logo.png' %}
|
|
||||||
{{ form.media }}
|
|
||||||
<style>
|
<style>
|
||||||
@media screen and (min-width: 40em) {
|
@media screen and (min-width: 40em) {
|
||||||
#sustainer-grid-wrapper {
|
#sustainer-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
grid-template-rows: min-content 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
progress {
|
||||||
|
background: #ddd;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
progress::-moz-progress-bar {
|
||||||
|
background: #224c57;
|
||||||
|
}
|
||||||
|
progress::-webkit-progress-bar {
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
progress::-webkit-progress-value {
|
||||||
|
background: #224c57;
|
||||||
|
}
|
||||||
.btn:active {
|
.btn:active {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
.errorlist li {
|
|
||||||
color: #ff4136;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<script defer src="{% static "js/vendor/alpine-3.14.1.js" %}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="lh-title tc mt4 mb2">Become a Sustainer Now</h1>
|
<h1 class="lh-title tc mt4 mb0">Become a Sustainer Now</h1>
|
||||||
<p class="measure-wide center tc">Sustainers help us do our work in a strategic, long-term way.</p>
|
<p class="measure-wide center tc">Sustainers help us do our work in a strategic, long-term way.</p>
|
||||||
|
|
||||||
{% comment %}
|
<div id="sustainer-grid" class="mv4">
|
||||||
<div class="f5 measure-wide center tc mv4">
|
<div style="grid-row: 1 / span 2">
|
||||||
<p class="mt3 mb0"><em>“If software freedom is important to you, I can't think of a
|
|
||||||
more effective way to use your money than to support Conservancy.”</em></p>
|
|
||||||
<p class="tr">— <strong>Made Up Person</strong></p>
|
|
||||||
</div>
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
<div id="sustainer-grid-wrapper" class="mv4" style="grid-template-columns: 2fr 1fr; gap: 1.5rem">
|
|
||||||
<section class="mb4">
|
|
||||||
<noscript>
|
|
||||||
<div style="padding: 1rem; border: 2px solid #0f0; margin-bottom: 1.5rem">
|
|
||||||
<p><marquee><strong>Hey there!</strong></marquee> Thanks for visiting our site <strong>without JavaScript</strong>!</p>
|
|
||||||
|
|
||||||
<p>We do our best to ensure our site works without JavaScript or, where necessary, to use only free software JavaScript.</p>
|
|
||||||
|
|
||||||
<p>The bad news is that all credit card/ACH payment services that are available to us, like Stripe and PayPal, <strong>don't work without JavaScript</strong>. We also don't currently have the resources to handle PCI compliant credit-card processing directly.</p>
|
|
||||||
|
|
||||||
<p>You can still become a Sustainer by making a payment by <a href="#wire-transfer">wire transfer</a> or <a href="#paper-check">paper check</a>. If those aren't feasible, please <a href="mailto:donate@sfconservancy.org">get in touch</a> and we'll try to work something out. Thanks for your support! And we think you are very cool!</p>
|
|
||||||
|
|
||||||
<img src="{% static 'img/dancing-banana.gif' %}" alt="Dancing Banana">
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
|
|
||||||
{# Alpine JS is used to show different payments amounts for monthly/annual, write the selected payment amount into the "amount" field, reset the seleted amount when you change monthly/annual and pop out the address when you select a T-shirt. #}
|
|
||||||
<form method="post" action="."
|
|
||||||
{# Pre-fill field defaults in case of server-side validation error. Otherwise Alpine JS will override them. Could alternatively use the `json_script` tag here. #}
|
|
||||||
x-data="{
|
|
||||||
recurring: '{{ form.recurring.value|escapejs }}',
|
|
||||||
amount: parseInt('{{ form.amount.value|escapejs }}'),
|
|
||||||
amount_option: '{{ form.amount_option.value|escapejs }}',
|
|
||||||
amount_options: function() {
|
|
||||||
let month_options = {{ form.MONTH_OPTIONS|escapejs }};
|
|
||||||
let year_options = {{ form.YEAR_OPTIONS|escapejs }};
|
|
||||||
return this.recurring === 'month' ? month_options : year_options;
|
|
||||||
},
|
|
||||||
amount_minimum: function() {
|
|
||||||
let month_minimum = {{ form.MONTH_MINIMUM|escapejs }};
|
|
||||||
let year_minimum = {{ form.YEAR_MINIMUM|escapejs }};
|
|
||||||
return this.recurring === 'month' ? month_minimum : year_minimum;
|
|
||||||
},
|
|
||||||
tshirt_size: '{{ form.tshirt_size.value|escapejs }}',
|
|
||||||
}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset class="bg-black-05 pa3 br3 center" style="border: 1px solid #ccc">
|
|
||||||
<legend class="b f5">Become a Sustainer</legend>
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
|
|
||||||
<div>{{ form.recurring.as_field_group }}</div>
|
|
||||||
|
|
||||||
<div class="mt3">
|
|
||||||
<div id="amount_options" class="button-select">
|
|
||||||
<template x-for="m in amount_options">
|
|
||||||
{# Additional click handler ensures a click-drag activates the radio (similar to a real button). #}
|
|
||||||
<label onclick="this.click()">
|
|
||||||
{# All radios have a unique value to avoid UI glitches (even though the value isn't actually used). #}
|
|
||||||
<input type="radio" name="amount_option" x-bind:value="m" x-on:change="amount = m" x-model="amount_option" required>
|
|
||||||
<span>$<span x-text="m.toLocaleString()"></span></span>
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
<!-- Hide if no JS -->
|
|
||||||
<template x-if="true">
|
|
||||||
<label onclick="this.click()">
|
|
||||||
<input type="radio" name="amount_option" value="other" x-on:change="amount = ''" x-model="amount_option" required>
|
|
||||||
<span>Other</span>
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="mt2" x-show="amount_option === 'other'">
|
|
||||||
{{ form.amount.as_field_group }}
|
|
||||||
<p class="f7 black-60 mt1">Minimum $<span x-text="amount_minimum"></span>. <a href="/donate" class="black-60">Donate smaller amounts here</a>.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt3">{{ form.name.as_field_group }}</div>
|
|
||||||
|
|
||||||
<div class="mt2">
|
|
||||||
{{ form.email.as_field_group }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt3"><label class="lh-title">{{ form.acknowledge_publicly }} Acknowledge me on the <a href="/sponsors#sustainers" target="_blank">list of sustainers</a></label></div>
|
|
||||||
|
|
||||||
<div class="mt3"><label class="lh-title">{{ form.add_to_mailing_list }} Add me to the <a href="https://lists.sfconservancy.org/pipermail/announce/">announcements email list</a></label></div>
|
|
||||||
|
|
||||||
<div class="mt3">
|
|
||||||
{{ form.tshirt_size.as_field_group }}
|
|
||||||
<p class="f7 black-60 mt1">Sizing chart:
|
|
||||||
<a href="/videos/women-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Women's</a>,
|
|
||||||
<a href="/videos/men-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Men's</a></p>
|
|
||||||
<figure class="mt2">
|
|
||||||
<a href="{% static 'img/tshirt-2024.png' %}">
|
|
||||||
<img src="{% static 'img/tshirt-2024.png' %}" alt="Software Freedom Conservancy T-shirt" width="200">
|
|
||||||
</a>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="address" x-show="tshirt_size !== ''">
|
|
||||||
<fieldset class="mt3">
|
|
||||||
<legend>Postal address</legend>
|
|
||||||
<div>{{ form.street.as_field_group }}</div>
|
|
||||||
<div class="mt2">{{ form.city.as_field_group }}</div>
|
|
||||||
<div class="mt2">{{ form.state.as_field_group }}</div>
|
|
||||||
<div class="mt2">{{ form.zip_code.as_field_group }}</div>
|
|
||||||
<div class="mt2">{{ form.country.as_field_group }}</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt3"><button type="submit" class="pointer btn f5 pv2" style="width: 100%; font-weight: bold; color: white; background-color: var(--orange); border-radius: 0.5rem; border: none; border-bottom: 2px solid rgba(0,0,0,0.1);">Become a Sustainer by<br>Credit Card or <abbr title="US Bank Direct Debit">ACH</abbr></button></div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p class="f7 mt3">Credit card and ACH payments are processed with Stripe. We also accept payment by PayPal, paper check and wire transfer.</p>
|
|
||||||
|
|
||||||
<details id="paypal">
|
|
||||||
<summary class="f6">PayPal</summary>
|
|
||||||
<p>If you would prefer not to use our Stripe payment service above you can use <a href="{% url 'sustainer_paypal' %}">PayPal</a>.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details id="wire-transfer">
|
|
||||||
<summary class="f6">Wire Transfer</summary>
|
|
||||||
<p>Contact <a href="mailto:donate@sfconservancy.org">Conservancy
|
|
||||||
by email</a> for wire transfer instructions. Include currency & country.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details id="paper-check">
|
|
||||||
<summary class="f6">Paper Check</summary>
|
|
||||||
<p>Send a paper check to:</p>
|
|
||||||
<p>Software Freedom Conservancy, Inc.<br>
|
|
||||||
137 MONTAGUE ST STE 380<br>
|
|
||||||
BROOKLYN, NY 11201-3548 USA</p>
|
|
||||||
<p>Please write <q>SUSTAINER</q>, T-shirt size, if you are renewing, and if
|
|
||||||
you want public acknowledgment in memo line.</p>
|
|
||||||
</details>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section style="grid-row: 1 / span 2">
|
|
||||||
<video controls poster="https://sfconservancy.org/videos/sfc-introduction-video_poster.jpg" class="mb3">
|
<video controls poster="https://sfconservancy.org/videos/sfc-introduction-video_poster.jpg" class="mb3">
|
||||||
<source src="https://sfconservancy.org/videos/sfc-introduction_1080p.mp4">
|
<source src="https://sfconservancy.org/videos/sfc-introduction_1080p.mp4">
|
||||||
<track src="/docs/sfc-introduction-vtt-captions.txt" kind="subtitles" srclang="en" label="English">
|
<track src="/docs/sfc-introduction-vtt-captions.txt" kind="subtitles" srclang="en" label="English">
|
||||||
|
@ -171,105 +46,248 @@
|
||||||
<a href="https://youtu.be/yCCxMfW0LTM">(watch on Youtube)</a>
|
<a href="https://youtu.be/yCCxMfW0LTM">(watch on Youtube)</a>
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
Year In Review 2024
|
<h3>The wide range of work we engage in is supported by people like you.</h3>
|
||||||
|
|
||||||
|
<p>We are so proud that we're funded by individuals and stay unbeholden to corporate interests and pressures. We stand up for developers, consumers and those who have been historically excluded. We work to make technology truly fair for all. </p>
|
||||||
|
|
||||||
<h3>The wide range of work we engage in is supported by people like you.</h3>
|
<p>Thank you for helping making this work possible:</p>
|
||||||
|
|
||||||
<p>We are so proud that we're funded by individuals and stay unbeholden to corporate interests and pressures. We stand up for developers, consumers and those who have been historically excluded. We work to make technology truly fair for all. </p>
|
|
||||||
|
|
||||||
<p>Thank you for helping making this work possible:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
|
|
||||||
<li>Standing up for consumer rights in <a href="/copyleft-compliance/">copyleft compliance</a> and the software right to repair</li>
|
|
||||||
|
|
||||||
<li>Supporting <a href="https://outreachy.org">Outreachy</a> and expanding the reach of the program with new communities</li>
|
|
||||||
|
|
||||||
<li>Bringing <a href="/vizio">legal action against prolific license violators</a></li>
|
|
||||||
|
|
||||||
<li>OpenWrt releases their first hardware project the <a href="https://one.openwrt.org/">OpenWrt One</a> (which will be generally available by the end of the year)</li>
|
|
||||||
|
|
||||||
<li>Hiring team members to get projects <a href="https://reproducible-builds.org/news/">like Reproducible Builds</a> to continue pushing the forefront of software reproducibility</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Standing up for consumer rights in <a href="/copyleft-compliance/">copyleft compliance</a></li>
|
||||||
|
<li>Supporting <a href="https://outreachy.org">Outreachy</a> with its increasing number of interns</li>
|
||||||
|
<li>Bringing <a href="/vizio">legal action against prolific license violators</a></li>
|
||||||
|
<li>Hiring team members to get projects <a href="https://reproducible-builds.org/news/">like Reproducible Builds</a> to continue pushing the forefront of software reproducibility</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h3 id="HelpUs">Help us Continue this Work</h3>
|
<h3 id="HelpUs">Help us Continue this Work</h3>
|
||||||
|
|
||||||
<p>We are beyond thankful for the ability to continue our work — which only continues due to <strong>your financial contributions</strong>. We recognize that not everyone has the same financial leeway to donate as they have in the past. But please consider giving what you can so that our organization can continue to advocate and support the rights of all software users. We work hard and efficiently, and accomplish so much with our small staff. We hope — through our hard work, creativity, and passionate dedication — that we've demonstrated over the years how Software Freedom Conservancy continues to be the beacon of change for software freedom that the world needs. <a href="/donate/">Please consider donating now!</a></p>
|
<p>We are beyond thankful for the ability to continue our work — which
|
||||||
|
only continues due to <strong>your financial contributions</strong>. We
|
||||||
|
recognize that not everyone has the same financial leeway to donate as they
|
||||||
|
have in the past. But please consider giving what you can so that our
|
||||||
|
organization can continue to advocate and support the rights of all
|
||||||
|
software users. We work hard and efficiently, and accomplish so much with
|
||||||
|
our small staff. We hope — through our hard work, creativity, and
|
||||||
|
passionate dedication — that we've demonstrated over the years how
|
||||||
|
Software Freedom Conservancy continues to be the beacon of change for
|
||||||
|
software freedom that the world needs. <a href="/donate/">Please consider
|
||||||
|
donating now!</a></p>
|
||||||
|
|
||||||
|
|
||||||
<h2 class="">2024 in Review</h2>
|
|
||||||
<details id="YearInReview">
|
<details id="YearInReview">
|
||||||
|
<summary>Our Year in Review</summary>
|
||||||
|
|
||||||
<summary>Overview</summary>
|
<p>This has been a big year for Software Freedom Conservancy in our tireless
|
||||||
|
efforts to promote ethical technology, increase diversity and inclusion in
|
||||||
|
FOSS, continuing to fight for your rights with <a
|
||||||
|
href="https://sfconservancy.org/copyleft-compliance/">copyleft compliance</a>,
|
||||||
|
and support our incredible <a href="https://sfconservancy.org/members/current/">member projects</a>.
|
||||||
|
Our staff engaged in many invited speaking opportunities, we grew our staff,
|
||||||
|
and we continue to build community around important issues like the software
|
||||||
|
Right to Repair movement. We hosted our first large conference, <a href="https://fossy.us">FOSSY</a>,
|
||||||
|
and while we finalize details for next year, we hope to see you there to join
|
||||||
|
us in community!</p>
|
||||||
|
|
||||||
|
<div class="picture-small right"> <img src="https://nextcloud.sfconservancy.org/apps/files_sharing/publicpreview/pnZYsi2CkjscLwc?file=/&fileId=24825&x=1366&y=768&a=true&etag=f4341a40f90786b0356201c21278ee23" alt="SFC lawyers posing outside at the courthouse“ " /></a>
|
||||||
|
<p>SFC lawyers after recent Vizio case- CC BY-SA 4.0</p></div>
|
||||||
|
|
||||||
<p>2024 has been a jam packed year at Software Freedom Conservancy; from the <a href="https://sfconservancy.org/news/2024/feb/03/use-the-source-launched/">new copyleft compliance efforts</a>, <a href="https://www.outreachy.org/blog/2024-07-19/outreachy-impact-ahmed-rafiat/">continued community building at Outreachy</a> and new venues of outreach and advocacy in the <a href="https://sfconservancy.org/blog/2024/feb/03/ccirt-security-and-software-right-to-repair/">software right to repair</a> world. There is growing interest in why software freedom is important for us all, and as we learn from and work with all of you about how to affect the change needed, we strive for making the world a more technologically inclusive and human centered place.</p>
|
<p>Our <a href="https://vizio.sfconservancy.org">lawsuit against Vizio</a>— the first
|
||||||
|
lawsuit of its kind to seek <b>third party beneficiary rights to the
|
||||||
|
complete, corresponding source code under the GPL</b> is progressing!
|
||||||
|
World-changing, public policy litigation requires resources and time, but
|
||||||
|
it's worth every penny so we can build a future where <b>every consumer</b> has
|
||||||
|
access to the source code they need to modify and repair their devices. At a
|
||||||
|
<a href="https://sfconservancy.org/blog/2023/oct/12/how-i-watched-motion-summary-judgment-hearing/">recent hearing in the case</a>,
|
||||||
|
we presented
|
||||||
|
<a href="https://sfconservancy.org/news/2023/oct/12/transcript-msj-hearing/">our opposition to Vizio's Summary Judgement Motion</a>,
|
||||||
|
our lawyers
|
||||||
|
<a href="https://sfconservancy.org/docs/Transcript_Full_Vizios_MSJ_HearingDeptC-33.231005.pdf">presented the eloquent case</a>
|
||||||
|
that recipients of software under the GPL Agreements <b>can</b> enforce their
|
||||||
|
rights to the corresponding source code themselves, and that right should not
|
||||||
|
be preempted by copyright law. We are the only organization currently taking
|
||||||
|
legal action to protect the GPL Agreements in the USA; your support is
|
||||||
|
critical for us to continue these kinds of efforts.</p>
|
||||||
|
|
||||||
<p><b>Outreachy</b> accepted 58 interns in the December 2023 cohort, and 35 interns in the May 2024 cohort with over 30 Free and Open Source software communities. Bringing in new communities in the Open Science and Humanitarian spheres, Outreachy continues to lead the way in providing opportunities to people subject to systemic bias and impacted by underrepresentation in technology. Last year celebrated our 1000th intern (!!!), and we've continued to provide these critical opportunities to people who need them most.</p>
|
<p>We have also <a href="https://sfconservancy.org/news/2020/jul/30/refile2020/">once again</a>
|
||||||
|
filed key DMCA exemptions for the following categories:</p>
|
||||||
<p>Since last year at this time, we spent over <b>$1.4 million</b> that we raised, administered and/or facilitated to improve software freedom directly! This includes contractors, interns and students, administrators, and grants for creation, distribution and maintenance of free software projects. Part of the unique position of our organization is the expertise necessary to do this kind of work.</p>
|
|
||||||
|
|
||||||
<p>Among our many avenues for making change, SFC works with our projects to host conferences, <a href="https://one.openwrt.org/">release hardware</a> (which will be generally available by the end of the year) and <a href="https://inbox.sourceware.org/overseers/20240529190215.GA26515@gnu.wildebeest.org/">continue providing important hosting and infrastructure services</a> to the free software world. Our member projects provide ways to assert our freedom over our technology and pursue important work in expanding the ways that we make free software available for everyone. Through <a href="https://coreboot.org/">liberating firmware</a>, <a href="http://microblocks.fun/">educational software</a>, creating <a href="https://backdropcms.org/">freedom respecting content hosting software</a>, and leading development in <a href="https://reproducible-builds.org/">supply chain security</a>.</p>
|
|
||||||
|
|
||||||
<div class="picture-small right">
|
|
||||||
|
|
||||||
<img class="small-right" src="/static/img/fossy-osl-panel.jpg" alt="Picture of AV team with panel sitting on stage at FOSSY" /> <p><a href="https://osuosl.org">OSUOSL</a> alumni panel with director Lance Albertson</p> <p>CC BY-SA 4.0 Samhir Vasdev</p></div>
|
|
||||||
|
|
||||||
|
|
||||||
<p>This year we hosted the second annual <a href="https://fossy.us">FOSSY</a> in Portland, Oregon. Over 15 tracks and speakers from over 10 countries, our event continues to grow and expand. Help us build the next big North American free software event by <a href="https://sfconservancy.org/fossy/community-tracks/">submitting track ideas<a/> or <a href="xmpp:fossy@chat.sfconservancy.org?join">joining our XMPP room</a>. Next year we'll be having it again <b>July 31st - August 3rd</b>, so mark your calendars!</p>
|
|
||||||
|
|
||||||
<p>All year long, we've worked tirelessly on our monumental and ground-breaking software Right to Repair litigation, which will establish a third-party right to enforce copyleft licenses (such as the GPL and LGPL). During our fundraiser this year, we'll release various interesting items from this litigation against Vizio — so those of you who are litigation geeks will have interesting reading this season!</p>
|
|
||||||
|
|
||||||
<p>In this years DMCA triennial we once again <a href="https://sfconservancy.org/news/2024/oct/31/success-in-2024-dmca-exemptions/">won exemptions for all four of the categories that we applied for</a>! Thanks to our Director of Compliance, Denver Gingerich for providing live expert testimony to the Library of Congress in the hearing.</p>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Computer programs for purposes of good-faith security research</li>
|
<li>Computer programs for purposes of good-faith security research</li>
|
||||||
<li>"jailbreaking" smart TVs</li>
|
<li>"jailbreaking" smart TVs</li>
|
||||||
<li>"jailbreaking" routers</li>
|
<li>"jailbreaking" routers</li>
|
||||||
<li>Literary works consisting of compilations of data generated by medical devices or their personal corresponding monitoring systems, to access personal data</li>
|
<li>Literary works consisting of compilations of data generated by medical devices or their personal corresponding monitoring systems, to access personal data</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>These allow people access (that should be granted in the first place!) to investigate their own devices for issues of license compliance, security,and replacing the firmware on their TVs and routers. It's a key ability to vet and replace software on devices we own and operate, so renewing this exemption each time it comes up is very important for us all. The medical device exemption was signed by our Executive Director Karen Sandler, on behalf of a group of medical device researchers who are personally affected by proprietary medical technology.</p>
|
<p>These allow people access (that should be granted in the first place!) to
|
||||||
|
investigate their own devices for issues of license compliance, security,
|
||||||
|
and replacing the firmware on their TVs and routers. It's a key ability to
|
||||||
|
vet and replace software on devices we own and operate, so renewing this
|
||||||
|
exemption each time it comes up is very important for us all. The medical
|
||||||
|
device exemption was submitted by our Executive Director Karen Sandler in
|
||||||
|
partnership with others who are personally affected by proprietary medical
|
||||||
|
technology.</p>
|
||||||
|
|
||||||
|
<p>As software based technology becomes more pervasive in our lives, it's vital
|
||||||
|
that we communicate the importance of software freedom to the wider population.
|
||||||
|
In that vein <a href="https://sfconservancy.org/news/2022/may/12/introduction/">we've created a video</a>
|
||||||
|
(narrated by our Executive Director Karen Sandler) that introduces
|
||||||
|
the ideas of software freedom, and specifically what Software Freedom
|
||||||
|
Conservancy does. We also did a lot of public writings about some of the
|
||||||
|
important issues this year. From <a href="https://sfconservancy.org/news/2023/feb/09/kuhn-neo4j-purethink-expert-report/">our perspective on the Neo4j suit</a>
|
||||||
|
to <a href="https://sfconservancy.org/news/2023/aug/15/exit-zoom/">highlighting problematic behavior from proprietary software companies</a> and calling for folks to Exit Zoom. We'd also like to
|
||||||
|
remind you that as a Sustainer, we will provide you with your own <a href="https://bbb.sfconservancy.org">BigBlueButton</a> account so that you can host your own video calls on a FOSS platform. Once
|
||||||
|
you have donated to us, you can fill our the new account signup and your
|
||||||
|
account will be approved.</p>
|
||||||
|
|
||||||
<div class="picture-small right">
|
<div class="picture-small right">
|
||||||
<a href="https://sfconservancy.org/videos/sfc-introduction_1080p.mp4"><img src="https://sfconservancy.org/videos/sfc-introduction-video_poster.jpg" alt="Thumbnail of video showing a tree and Software Freedom" /></a></div>
|
<a href="https://sfconservancy.org/videos/sfc-introduction_1080p.mp4"><img src="https://sfconservancy.org/videos/sfc-introduction-video_poster.jpg" alt="Thumbnail of video showing a tree and Software Freedom" /></a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
<p>As software based technology becomes more pervasive in our lives, it's vital that we communicate the importance of software freedom to the wider population. <a href="https://sfconservancy.org/news/2022/may/12/introduction/">We have a video</a>(narrated by our Executive Director Karen Sandler) that introduces the ideas of software freedom, and specifically what Software Freedom Conservancy does. Continuing to advocate against corporate controlled software, we'd also like to remind you that as a <b>Sustainer</b>, we will provide you with your own <a href="https://bbb.sfconservancy.org">BigBlueButton</a> account so that you can host your own video calls on a FOSS platform. Once you have donated to us, you can fill our the new account signup and your account will be approved.</p>
|
<details id="NewStaff">
|
||||||
|
<summary>New staff!</summary>
|
||||||
|
<p>SFC hired two additional employees this year! General Counsel Rick Sanders
|
||||||
|
joins the team to help with our continued legal needs. Rick has over 20 years
|
||||||
|
experience as a intellectual-property litigator. His expertise has been
|
||||||
|
critical in helping our license compliance efforts and helping our organization
|
||||||
|
take on the increasing needs from projects and new initiatives. SFC's new
|
||||||
|
systems administrator is Paul Visscher. With over 20 years experience with
|
||||||
|
Linux and free software, Paul's belief in the power of free software to help
|
||||||
|
people engage with technology in non-exploitative ways fits in perfectly to
|
||||||
|
support our growing organizational needs and mission. Helping make sure we
|
||||||
|
can provide solid FOSS replacements to proprietary technologies for all of us.</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details id="WritingAndSpeaking">
|
<details id="WritingAndSpeaking">
|
||||||
|
<summary>Writing and Speaking</summary>
|
||||||
<summary>Writing and Speaking</summary>
|
<p>Our staff has been presenting and speaking about software freedom all year.
|
||||||
|
Our Executive Director Karen Sandler received an honorary doctorate from
|
||||||
<p>This year our staff attended and spoke at many conferences, including <a href="https://archive.fosdem.org/2024/schedule/event/fosdem-2024-2522-outreachy-1000-interns/">keynoting FOSDEM!</a>, speaking to both current issues by hosting <a href="https://archive.fosdem.org/2024/schedule/event/fosdem-2024-3668-hot-topics-organizers-of-the-legal-policy-devroom-discuss-the-issues-of-the-day/">a panel discussion at FOSDEM</a> and speaking about the <a href="https://www.sfscon.it/talks/25-years-of-copyleft-enforcement-the-road-ahead-for-defending-software-rights/">25 year history of copyleft compliance</a>. You probably also saw us at <a href="https://osseu2024.sched.com/event/1ej2W/panel-discussion-creating-your-community-mentorship-program-stephanie-taylor-google-abigail-cabunoc-mayes-github-and-hong-phuc-dang-fossasia?iframe=no&w=100%&sidebar=yes&bg=no">OSSEU</a>, <a href="https://www.socallinuxexpo.org/scale/21x/presentations/running-open-source-hackerspace">SCaLE</a>, <a href="https://fsfe.org/activities/ln/llw-conf.en.html">LLW</a> and many more. Our Executive Director Karen Sandler spoke at <a href="https://www.navapbc.com/events/open-source-summit-federal-health">NAVA Open Source Summit: Advancing IT Solutions in Federal Health and Beyond</a>, <a href="https://www.youtube.com/watch?v=dGEKHXqaz9E">Apereo microconf</a>, participating in the <a href="https://www.un.org/techenvoy/content/ospos-good-2024">UN OSPOs for Good event</a> being a content partner at <b>What's Next 4 OSS</b>, as well as other diversity and public health events. As we move into a new era of computing, we also want to make sure that our values are present in discussions of emerging technologies, that's why we released our <a href="https://sfconservancy.org/news/2024/oct/25/aspirational-on-llm-generative-ai-programming/">aspirational statement on generative AI</a>. SFC's advocacy for software freedom and digital rights is widening and spreading to more regional policy; our Director of Compliance recently spoke at <a href="https://www.canrepair.ca/convention">CanRepair</a>, the Canadian Repair Convention. Coalition building and community hosting is something we take so much pride in, and by hosting <a href="https://fossy.us">FOSSY</a> our own community focused conference. Hosting tracks, keynotes and giving talks, our staff was so excited to be back in Portland to celebrate software freedom with our attendees. <a href="https://sfconservancy.org/news/2024/oct/17/FOSSY-2025-announcement/">We want to hear from you</a> about what kind of tracks, talks and events you'd like to participate in. As we continue to ramp back into more face to face events, we're so excited for safe and inclusive events; you just can't beat being in community!</p>
|
Katholieke Universiteit Leuven for her incredible work in FOSS leadership, and
|
||||||
</details>
|
her advocacy and pursuit of software freedom and rights for all.
|
||||||
|
In November she spoke at <a href="https://www.sfscon.it/talks/the-history-of-and-path-forward-for-copyleft-and-the-gpl/">SFSCON about "The History of, and Path forward for, Copyleft and the GPL"</a>.</p>
|
||||||
<details id="Highlights">
|
|
||||||
|
|
||||||
<summary>Highlights From Our Member Projects</summary>
|
|
||||||
|
|
||||||
|
|
||||||
<p>As a fiscal host, our member projects are all our children and it's hard to pick a favorite :) Here are some highlights from the incredible member projects (members as we consider our relationship collaborative). Ranging from education software, to content management systems, copyright aggregation, free firmware and open compatibility software, SFC is home to an amazing gamut of incredibly important infrastructural and user facing free software projects.</p>
|
|
||||||
|
|
||||||
<p><b>The Institute for Computing in Research</b> completed it's sixth year, providing training, education and real world software experience to high school students in 3 cities and exploring additional cities that may join next summer. These research internships are a great and unique way for high school students to get involved in real academic research while also being exposed to the ideas and principles of software freedom.</p>
|
|
||||||
|
|
||||||
<div class="picture-small right">
|
<div class="picture-small right">
|
||||||
<img src="https://sfconservancy.org/static/img/one-PCB-small.jpg" alt="Picture of OpenWrt One PCB showing Software Freedom Conservancy logo and offer for source" /><p>Picture of OpenWrt One PCB showing Software Freedom Conservancy logo and offer for source CC-BY-SA 4.0 Denver Gingerich</p></a>
|
<video class="small-right" controls="" poster="/videos/2023-02-02_Sandler-Karen_KU-Leuven_Honorary-Doctorate_still.png" id="doctorate_vid">
|
||||||
|
<source src="/videos/2023-02-02_Sandler-Karen_KU-Leuven_Honorary-Doctorate.mp4">
|
||||||
|
<track src="/docs/2023-02-02_Sandler-Karen_KU-Leuven_Honorary-Doctorate.en.txt" kind="subtitles" srclang="en" label="English" />
|
||||||
|
<track src="/docs/2023-02-02_Sandler-Karen_KU-Leuven_Honorary-Doctorate.nl.txt" kind="subtitles" srclang="nl" label="Dutch (NL)" />
|
||||||
|
</video>
|
||||||
|
<p><a href="/videos/2023-02-02_Sandler-Karen_KU-Leuven_Honorary-Doctorate.mp4">Download Karen's talk</a>
|
||||||
|
or <a href="https://youtu.be/zca7dOU7jfs">watch on YouTube</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><b>OpenWrt</b> replaced their package manager OPKG <a href="https://forum.openwrt.org/t/major-change-notice-new-package-manager/215682">with Alpine Linux's apk</a> and with support from SFC, launched their first official hardware project called the <a href="https://one.openwrt.org/">OpenWrt One</a> (which will be generally available by the end of the year)! Being both reference hardware for the project as well as a shining example of providing making the <a href="https://one.openwrt.org/sources/">Complete Corresponding Source available</a>. We're so excited for the OpenWrt team for making this happen and increasing the amount of open source first products in the world.</p>
|
<p>Policy Fellow Bradley M. Kuhn gave many conference talks and also represented SFC at many
|
||||||
|
government hearings and inquiries. Beginning the year at <a href="https://archive.fosdem.org/2023/schedule/speaker/bradley_m_kuhn/">FOSDEM</a>,
|
||||||
|
Bradley (and Karen) led the Legal and Policy DevRoom. He then spoke at SCaLE
|
||||||
|
20x giving a talk titled <a href="https://www.socallinuxexpo.org/scale/20x/presentations/learning-big-failures-improve-foss-advocacy-and-adoption">Learning From the Big Failures To Improve FOSS Advocacy and Adoption</a>.
|
||||||
|
As the host of a keynote discussion at FOSSY, Bradley facilitated a
|
||||||
|
<a href="https://sfconservancy.org/blog/2023/jul/19/rhel-panel-fossy-2023/">
|
||||||
|
community discussion about the RHEL policy change</a>.
|
||||||
|
As a panel member, he was the only representative for the FOSS community on
|
||||||
|
the FTC's discussion <a href="https://sfconservancy.org/news/2023/oct/04/ftc-ai-panel/">“Creative Economy and Generative AI“</a></p>
|
||||||
|
|
||||||
<p><b>qemu</b> hosted <a href="https://kvm-forum.qemu.org/">KVM Forum</a> in Brno this year. Featuring talks from over 50 speakers which you can view <a href="https://kvm-forum.qemu.org/2024/">on the conference website</a>. qemu remains a hallmark project that is the industry standard for emulation and virtualization, on which so much of our technology relies.</p>
|
<p>Our Director of Compliance, Denver Gingerich, spoke at SFSCON talking about what it's really like to <a href="https://www.sfscon.it/talks/how-do-you-really-do-gpl-enforcement/">enforce the GPL</a>. His talk at FOSSY titled <a href="https://2023.fossy.us/schedule/presentation/84/">You don't carry a phone?! Improving societal acceptance of abnormal people</a>
|
||||||
|
was one of the most talked about from our own conference.
|
||||||
<p><b>git</b> participated in <a href="https://summerofcode.withgoogle.com/programs/2024/organizations/git">GSoC</a> with three students and hosted their <a href="https://git-merge.com">Git Merge conference</a> in Berlin this fall. Coming up on their 20th year, the project stands as a standard for free software development and just as importantly, using free software to develop itself.</p>
|
Pono represented SFC at <a href="https://www.socallinuxexpo.org/scale/19x">SCaLE
|
||||||
|
19x</a> and <a href="https://seagl.org">SeaGL</a>, which was great to be back
|
||||||
<p><b>Inkscape</b> had their huge <a href="https://inkscape.org/news/2024/10/13/inkscape-launches-version-14-powerful-new-accessib/">1.4 release!</a> A landmark project that celebrated their 21st year in 2024. Inkscape is <a href=" https://inkscape.org/news/2024/11/06/inkscape-21-growing-and-getting-organized/">looking toward a bright future</a> with plenty of community support and a growing user base.</p>
|
at community centered conferences.
|
||||||
|
SFC staffers also participate in key meetings to represent community interests
|
||||||
<p><b>Reproducible Builds</b> continued their incredible work with <a href="https://www.sovereigntechfund.de/tech/reproducible-builds">The Sovereign Tech Fund</a> to lead by example the practicality and importance of supply chain security. Giving <a href="https://reproducible-builds.org/resources/">talks in over 5 countries</a>, influencing <a href="https://reproducible-builds.org/reports/2024-09/#two-new-reproducibility-related-academic-papers">academic papers</a>, and hosting their <a href="https://reproducible-builds.org/events/hamburg2024/">annual summit</a>, it's been an incredibly busy year for the team!</p>
|
in a variety of FOSS related discussions concerning security, governmental use
|
||||||
|
of FOSS and in critical infrastructure discussions and also presented in
|
||||||
|
classroom to educate students about software freedom.</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
</section>
|
|
||||||
|
<details id="Highlights">
|
||||||
|
<summary>Highlights from some of our projects</summary>
|
||||||
|
<p>We've raised, administered and/or facilitated $1.8 million to improve
|
||||||
|
software freedom directly! This includes contractors, interns and students,
|
||||||
|
administrators, and grants for creation, distribution and maintenance of free
|
||||||
|
software projects. Part of the unique position of our organization is the
|
||||||
|
expertise necessary to do this kind of work.</p>
|
||||||
|
|
||||||
|
<div class="picture-small right">
|
||||||
|
<img class="small-right" src="{% static 'img/outreachy-nigeria-1000-celebration.png' %}"
|
||||||
|
alt="Group Picture: Outreachy interns, mentors, and community coordinators gathered to celebrate the 1,000 interns milestone in Lagos, Nigeria." />
|
||||||
|
<p>Group Picture: Outreachy interns, mentors, and community coordinators gathered to celebrate the 1,000 interns milestone in Lagos, Nigeria.</p>
|
||||||
|
<p>You can view a <a href="https://diode.zone/w/p/25ifUPw9Lx42nrb14h41Ru">video of the celebration here.</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p><b>Outreachy</b> accepted 63 interns in the December 2022 cohort, and 64
|
||||||
|
interns in the May 2023 cohort with over 30 Free and Open Source software
|
||||||
|
communities. Bringing in new communities in the Open Science and Humanitarian
|
||||||
|
spheres, Outreachy continues to lead the way in providing opportunities to
|
||||||
|
people subject to systemic bias and impacted by underrepresentation in
|
||||||
|
technology. Celebrating our 1000th intern (!!!), there were celebrations
|
||||||
|
featuring alumni and current interns all over the world. </p>
|
||||||
|
|
||||||
|
<p><b>OpenWrt</b> released <a href="https://openwrt.org/releases/23.05/notes-23.05.0">version 23.05.0</a>
|
||||||
|
which continued adding hardware support for myriad devices, now supporting over
|
||||||
|
1790 devices, over 200 of which were added since last year! There is also now
|
||||||
|
support for various Rust packages, and major improvements to the core
|
||||||
|
components. OpenWrt remains one of the most important alternative firmware
|
||||||
|
projects, ensuring user rights in the ability to install (free) software on
|
||||||
|
your own devices.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p><a href="https://computinginresearch.org/">The Institute for Computing in
|
||||||
|
Research</a> completed it's fifth year, supporting 32 students in 3 cities this
|
||||||
|
summer. Now providing training, education and real world software experience to
|
||||||
|
high school students in 3 cities and exploring additional cities that may join
|
||||||
|
next summer. These research internships are a great way for high school
|
||||||
|
students to get involved in real academic research while also being exposed to
|
||||||
|
the ideas and principles of software freedom.</p>
|
||||||
|
|
||||||
|
<p><a href="https://inkscape.org">Inkscape</a> just celebrated their 20th
|
||||||
|
anniversary! Coinciding with their new <a href="https://inkscape.org/news/2023/11/18/big-small-release-inkscape-131-is-out/">1.3.1 release</a>, Inkscape is as active as ever and proving itself one
|
||||||
|
of the most vital free software projects, full of longevity and an ever
|
||||||
|
growing community. This year has been a big year of development, marking some
|
||||||
|
very exciting developments for the project. Creation of a new <a href="https://inkscape.org/*membership/blog/october-bug-accelerator-2023/">bug accelerator program</a>, a migration to <a href="https://inkscape.org/*membership/blog/august-gtk4-migration-tavmjong/">GTK4</a>, another year of <a href="https://inkscape.org/*membership/blog/customizable-canvas-controls/">GSOC participation</a> and new support for <a href="https://inkscape.org/*membership/blog/may-ai-extension-2023/">Adobe Illustrator import</a>!</p>
|
||||||
|
|
||||||
|
<p>Our member projects had a range of in person and online events this year.
|
||||||
|
<b>Inkscape</b> hosted an in-person meeting in Bensberg, Germany. A great meeting
|
||||||
|
for the PLC and contributors to get together to plan and work on technical
|
||||||
|
challenges. The first back in-person <b>Selenium</b> <a
|
||||||
|
href="https://seleniumconf.com/">conference</a> was in Chicago this past
|
||||||
|
may</a>. Attendance from over 10 countries, it was an incredible reunion for
|
||||||
|
the project contributors and users to get together. The <b>Git</b>
|
||||||
|
contributor summit was held online this year in September. Topics ranged from
|
||||||
|
ideas of new library support to how to better support for scaling with large
|
||||||
|
code forges and what the new contributor experience is like. A great mix of
|
||||||
|
code related and process related talks. The <a href="https://reproducible-builds.org/">Reproducible Builds</a>
|
||||||
|
annual summit was hosted in Hamburg featuring incredible
|
||||||
|
technical talks, project planning and continues to build the momentum and
|
||||||
|
reach for reproducibility. </p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>Our sustainers</h3>
|
||||||
|
|
||||||
|
<p>Anonymous (100 people), Aurimas Fišeras, Kat Walsh, Richard Wheeler, Karl Ove Hufthammer, Mark Wielaard, Karl Fogel, Richard Fontana, Richard L. Schmeidler, Karen Sandler, Russ Allbery, Christine Webber, Jeremy Allison, J.B. Nicholson, Michael Dexter, Tom Marble, Johannes Krampf, Michael Linksvayer, Jack Hill, Stefano Zacchiroli, Daniel Callahan, Ben Cotton, in memory of Marina Zhurakhinskaya, Jim Radford, Tyng-Ruey Chuang, Francois Marier, Henri Sivonen, Keith Packard, Monica Neumann, Michal Nazarewicz, Bdale Garbee, David Neary, Alexander Bokovoy, Andrew Isaacson, Brian Hart, James Pannacciulli, Sasa Poznanovic, David Batley, David Crossland, Steve Sprang, Bob Murphy, Mark Galassi, James R. Garrison, Bluebird Interactive, David Quist, Patrick Masson, Neil McGovern, Lenore Ramm Hill, Paul Logston, David Arnold, Benjamin Pfaff, Timothy Howes, Britta Gustafson, Wookey, Michael Gulick, Tanu Kaskinen, Jeffrey Layton, Raphaël Hertzog, Will Thompson, Matteo Settenvini, Kevin Krammer, Elana Hashman, Richard Schultz, Charkov Alexey, Donald Craig, Michael Catanzaro, Olav Reinert, Stephen Kitt, Barry Fishman, Luigi Toscano, Steve McIntyre, Cornelia Huck, Jonathan McDowell, Emmanuel Seyman, Mike Crowe, Alexandre Julliard, Ross Vandegrift, Ian Jackson, Alexander Reichle-Schmehl, Sang Engineering, Preston Maness, John Hagemeister, Julien Cristau, Rebecca Sobol, John Hughes, Peter Link, Solomon Peachy, Riccardo Ghetta, Stefano Rivera, Julian Gilbey, Srivats P, JRS System Solutions, Eric Dorland, Matija Nalis, Brett Smith, Dmitry Borodaenko, Johannes Berg, Howard Shih, Nigel Whillier, Peter Maydell, Lars Wirzenius, Stephanie Feingold, Kevin Adler, Matthew Vernon, Stefan Seefeld, scrye.com, Robert Horn, Andreas Bombe, Michael Kuhn, Stephen Waite, Philip Cohn-Cort, Stuart Smith, Michel Machado, Joseph Thompson, Joan Sandler, Sage Ross, Peter Levy, Daniel Gillmor, James Carter, Wilson E. Alvarez, Michael Andreen, Aaron Puchert, Andrew Eikum, Vladimir Michl, Gregory Grossmeier, Josh Triplett, James Blair, Felix Gruber, Claire Connelly, Antoine Amarilli, Kenneth J. Pronovici, Igalia S. L., Karl-Johan Karlsson, David Gibson, Tom Callaway, Steven A. Ovadia, Gerard Ryan, James Garrett, William Norris, Luke Faraone, Christian Gelinek, Chris Neugebauer, David Potter, Paul Fenwick, George Bojanov, Jondale Stratton, Kiatikun Luangkesorn, hmk, Yu Tomita, Jure Varlec, Antonin Houska, Chad Henderson, Adam Batkin, Marc Jeanmougin, Mike Dowling, Nicholas George, Leif Lindholm, Diane Trout, Daniel Walls, Donald Anderson, Darrick Wong, Greg Price, Martin Krafft, Tony Sebro, Matthew Treinish, Jason Baker, Kathy Giori, Brennen Bearnes, Olly Betts, Steven Adger, John Maloney, Gargi Sharma, Andrew Janke, Andy Kittner, Holger Levsen, Jacopo Corbetta, Andy Balaam, Justin W. Flory, Albert Chae, Elias Rudberg, Gene Hightower, Asumu Takikawa, John-Isaac Greant, Ulrich Czekalla, Bob Proulx, Nick Alcock, Geoffrey Knauth, Luke Shumaker, Stephen Hinton, Philip McGrath, Anjandev Momi, Meisam Tabriz, Alex Dryden, Thomas Schwinge, Julia Kreger, nicholas Bishop, Rachel Wonnacott, Benjamin Kraus, David Witten, Pontus Ullgren, Brendan Horan, Alex Karle, Michael Pennisi, Dave Jansen, Kit Aultman, Jason Prince, Frank Eigler, Keyhan Vakil, Daniel Whiting, tam phan, Jon Stumpf, Anna Philips, Anthony Symkowick, Drew Fustini, Anthony Mirabella, Eric Perko, Simon Michael, Rod Nayfield, Joerg Jaspert, Lieven Govaerts, David Harris, BRUST, Alexander Couzens, Amisha Singla, Athul Iddya, kyle Davis, Trace Pearson, Paul Williams, Peter Murray, anne fonteyn</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<progress min="0" max="100" value="84.5" class="w-100">84.5%</progress>
|
||||||
|
<div class="mv3">
|
||||||
|
<div class="f2 b">$15,558</div>
|
||||||
|
<div class="f5 b black-50">Remaining of the $100,000 goal</div>
|
||||||
|
</div>
|
||||||
|
<div class="mv3">
|
||||||
|
<div class="f2 b">15 days</div>
|
||||||
|
<div class="f5 b black-50">Remaining</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt4">
|
||||||
|
<a href="{% url "stripe2" %}">
|
||||||
|
<button type="submit" class="pointer btn" style="height: 40px; width: 100%; font-size: 18px; font-weight: bold; color: white; background-color: var(--orange); border-radius: 0.5rem; border: none; border-bottom: 2px solid rgba(0,0,0,0.1);">Become a Sustainer!</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt3">
|
||||||
|
<figure>
|
||||||
|
<img src="/static/img/tshirt-2023.png" alt="Software Freedom Conservancy T-shirt">
|
||||||
|
<figcaption class="tc black-70" style="margin-top: -20px">Our new Sustainer T-shirt</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
{% extends "base_conservancy.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block subtitle %}Support Conservancy - {% endblock %}
|
||||||
|
{% block category %}sustainer{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ block.super }}
|
||||||
|
<script defer src="{% static "js/vendor/alpine-3.14.1.js" %}"></script>
|
||||||
|
<style>
|
||||||
|
.btn:active {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="lh-title tc mt4 mb0">Become a Sustainer Now</h1>
|
||||||
|
<p class="measure-wide center tc">Sustainers help us do our work in a strategic, long-term way.</p>
|
||||||
|
|
||||||
|
<div class="bg-black-05 pa3 br3 mb4 center" style="max-width: 24rem; border: 1px solid #ccc">
|
||||||
|
<form id="sustainer" method="post" action="."
|
||||||
|
x-data="{
|
||||||
|
tshirt_size: 'None',
|
||||||
|
tshirt_required: function () { return this.tshirt_size !== 'None' },
|
||||||
|
recurring: 'once',
|
||||||
|
}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.errors }}
|
||||||
|
<div class="mb2"><label>Name
|
||||||
|
<span class="db mt1">{{ form.name }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2"><label>Email
|
||||||
|
<span class="db mt1">{{ form.email }}</span>
|
||||||
|
</label>
|
||||||
|
<p class="f7 black-60 mt1">To send your receipt</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb2"><label>
|
||||||
|
<label class="mr1"><input type="radio" name="recurring" value="" x-model="recurring"> Once</label>
|
||||||
|
<label class="mr1"><input type="radio" name="recurring" value="month" x-model="recurring"> Monthly</label>
|
||||||
|
<label><input type="radio" name="recurring" value="year" x-model="recurring"> Annual</label>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2" x-show="recurring === ''"><label>Amount
|
||||||
|
<span class="db mt1">$ {{ form.amount }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2" x-show="recurring === 'month'"><label>Amount
|
||||||
|
<span class="db mt1">$ {{ form.amount_monthly }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2" x-show="recurring === 'year'"><label>Amount
|
||||||
|
<span class="db mt1">$ {{ form.amount }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mv3"><label class="lh-title">{{ form.acknowledge_publicly }} Acknowledge me on the public <a href="">list of sustainers</a></label></div>
|
||||||
|
<div class="mv3"><label class="lh-title">{{ form.add_to_mailing_list }} Add me to the low-traffic <a href="https://lists.sfconservancy.org/pipermail/announce/">announcements</a> email list</label></div>
|
||||||
|
<div class="mv3">
|
||||||
|
<label>T-shirt:
|
||||||
|
<!-- Form field has an x-model attribute in forms.py. -->
|
||||||
|
<span class="db mt1">{{ form.tshirt_size }}</span>
|
||||||
|
</label>
|
||||||
|
<p class="f7 black-60 mt1">Sizing:
|
||||||
|
<a href="https://sfconservancy.org/videos/women-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Women's</a>,
|
||||||
|
<a href="https://sfconservancy.org/videos/men-2017-to-2020-t-shirt-sizing.jpg" target="_blank" class="black-60">Men's</a></p>
|
||||||
|
</div>
|
||||||
|
<!-- Using Alpine.js to show/hide the address based on T-shirt choice. -->
|
||||||
|
<template x-if="tshirt_required">
|
||||||
|
<fieldset id="address">
|
||||||
|
<legend>Postal address</legend>
|
||||||
|
<div class="mb2"><label>Street
|
||||||
|
<span class="db mt1">{{ form.street }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2"><label>City
|
||||||
|
<span class="db mt1">{{ form.city }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2"><label>State/Region
|
||||||
|
<span class="db mt1">{{ form.state }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2"><label>Zip/Postal
|
||||||
|
<span class="db mt1">{{ form.zip_code }}</span>
|
||||||
|
</label></div>
|
||||||
|
<div class="mb2"><label>Country
|
||||||
|
<span class="db mt1">{{ form.country }}</span>
|
||||||
|
</label></div>
|
||||||
|
</fieldset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="mt3"><button type="submit" class="btn" style="height: 40px; width: 100%; font-size: 18px; font-weight: bold; color: white; background-color: var(--orange); border-radius: 0.5rem; border: none; border-bottom: 2px solid rgba(0,0,0,0.1);">Pay via Stripe</button></div>
|
||||||
|
|
||||||
|
<p class="f7 mt3">If you have concerns or issues paying with Stripe, we also accept payment by <a href="#">paper check</a> and <a href="#">wire transfer</a>.</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -4,11 +4,11 @@ from django.views.generic import TemplateView
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.sustainers_stripe, name='sustainers'),
|
path('', views.sustainers),
|
||||||
path('banner/', TemplateView.as_view(template_name='supporters/banners.html')),
|
path('banner/', TemplateView.as_view(template_name='supporters/banners.html')),
|
||||||
path('banners/', TemplateView.as_view(template_name='supporters/banners.html')),
|
path('banners/', TemplateView.as_view(template_name='supporters/banners.html')),
|
||||||
path('success/', views.success),
|
path('success/', views.success),
|
||||||
path('webhook/', views.webhook),
|
path('webhook/', views.webhook),
|
||||||
# TODO
|
path('stripe/', views.sustainers_stripe),
|
||||||
path('paypal/', views.sustainers_paypal, name='sustainer_paypal'),
|
path('stripe2/', views.sustainers_stripe2, name='stripe2'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
import datetime
|
from datetime import datetime
|
||||||
import decimal
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import transaction
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import stripe
|
import stripe
|
||||||
|
|
||||||
from .. import ParameterValidator
|
from .. import ParameterValidator
|
||||||
from . import forms, mail
|
from . import forms
|
||||||
from .models import Supporter, SustainerOrder, SustainerPayment
|
from .models import Supporter, SustainerOrder
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def sustainers(request):
|
def sustainers(request):
|
||||||
with ParameterValidator(request.GET, 'upgrade_id') as validator:
|
with ParameterValidator(request.GET, 'upgrade_id') as validator:
|
||||||
try:
|
try:
|
||||||
|
@ -37,23 +33,20 @@ def sponsors(request):
|
||||||
|
|
||||||
Performs object queries necessary to render the sponsors page.
|
Performs object queries necessary to render the sponsors page.
|
||||||
"""
|
"""
|
||||||
supporters = Supporter.objects.all().filter(display_until_date__gte=datetime.datetime.now())
|
supporters = Supporter.objects.all().filter(display_until_date__gte=datetime.now())
|
||||||
supporters_count = len(supporters)
|
supporters_count = len(supporters)
|
||||||
anonymous_count = len(supporters.filter(display_name='Anonymous'))
|
anonymous_count = len(supporters.filter(display_name='Anonymous'))
|
||||||
supporters = supporters.exclude(display_name='Anonymous').order_by('ledger_entity_id')
|
supporters = supporters.exclude(display_name='Anonymous').order_by('ledger_entity_id')
|
||||||
c = {
|
c = {
|
||||||
'supporters': supporters,
|
'supporters' : supporters,
|
||||||
'supporters_count': supporters_count,
|
'supporters_count' : supporters_count,
|
||||||
'anonymous_count': anonymous_count,
|
'anonymous_count' : anonymous_count
|
||||||
}
|
}
|
||||||
return render(request, "supporters/sponsors.html", c)
|
return render(request, "supporters/sponsors.html", c)
|
||||||
|
|
||||||
|
|
||||||
def create_checkout_session(
|
def create_checkout_session(reference_id, email: str, amount: int, recurring: str, base_url: str):
|
||||||
reference_id, email: str, amount: int, recurring: str, base_url: str
|
|
||||||
):
|
|
||||||
# https://docs.stripe.com/payments/accept-a-payment
|
# https://docs.stripe.com/payments/accept-a-payment
|
||||||
# https://docs.stripe.com/api/checkout/sessions
|
|
||||||
YOUR_DOMAIN = base_url
|
YOUR_DOMAIN = base_url
|
||||||
try:
|
try:
|
||||||
checkout_session = stripe.checkout.Session.create(
|
checkout_session = stripe.checkout.Session.create(
|
||||||
|
@ -80,250 +73,78 @@ def create_checkout_session(
|
||||||
return checkout_session.url
|
return checkout_session.url
|
||||||
|
|
||||||
|
|
||||||
def sustainers_paypal(request):
|
|
||||||
return render(request, 'supporters/sustainers_paypal.html')
|
|
||||||
|
|
||||||
|
|
||||||
# Sustainers via Stripe
|
|
||||||
# =====================
|
|
||||||
#
|
|
||||||
# Background and problem
|
|
||||||
# ----------------------
|
|
||||||
#
|
|
||||||
# Conservancy accepts both one-off and monthly/annual recurring sustainer
|
|
||||||
# payments. Currently we used PayPal for this to avoid the compliance work and cost
|
|
||||||
# associated with PCI compliance. The relevant sustainer details and are sent to PayPal
|
|
||||||
# as custom fields, the donor pays via the PayPal hosted payment form, receives a
|
|
||||||
# receipt from PayPal and then later Bradley runs a batch script that takes PayPal data
|
|
||||||
# and sends a custom thanks email. (Where does the data come from? Is it a dashboard
|
|
||||||
# export or an API call?)
|
|
||||||
#
|
|
||||||
# The problem here is firstly that PayPal are difficult and somewhat risky to deal with
|
|
||||||
# in a business sense - they have been known to shut you down on a whim. Secondly we're
|
|
||||||
# heavily tied to PayPal - we're using them as a sustainer database to capture things
|
|
||||||
# like T-shirt size and address before these are imported into Bradley's
|
|
||||||
# supporter.db. To be less tied to PayPal, we would need to capture these details in our
|
|
||||||
# own database and only pass the necessary minimum details to the payment provider to
|
|
||||||
# take the payment (ie. email and payment amount).
|
|
||||||
#
|
|
||||||
# We also use PayPal to manage billing for recurring monthly/annual subscriptions, but
|
|
||||||
# that's less of an issue because that's more difficult to do reliably ourselves.
|
|
||||||
#
|
|
||||||
# We would like to integrate Stripe as a payment provider, possibly eventually replacing
|
|
||||||
# PayPal entirely for new sustainers. We have to be careful though. While Stripe were
|
|
||||||
# once focused on just accepting credit card payments, they've now moved into the
|
|
||||||
# billing and "financial automation" market so we could easily tie ourselves to Stripe
|
|
||||||
# if we're not careful.
|
|
||||||
#
|
|
||||||
# The first thing we need to do is keep our own database of sustainer orders. When a
|
|
||||||
# sustainer signs up, we record all their information and unpaid order status there and
|
|
||||||
# pass only the necessary info across to Stripe.
|
|
||||||
#
|
|
||||||
# The second thing is to produce a CSV of payments to be processed by Bradley's
|
|
||||||
# fulfilment scripts that creates Beancount bookkeeping entries, updates the
|
|
||||||
# acknowledgements on the sponsors page and determines who to send a T-shirts to (not
|
|
||||||
# immediate for monthly donors).
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Approach to integrating with Stripe
|
|
||||||
# -----------------------------------
|
|
||||||
#
|
|
||||||
# The simplest approach to integrate Stripe seems to be to use their hosted checkout
|
|
||||||
# page. It's a currently recommended approaches as of 2024 (ie. not legacy),
|
|
||||||
# requires relatively little code, can handle complicated bank verification processes
|
|
||||||
# like 3-D Secure, allows us to switch on/of additional payment methods such as
|
|
||||||
# ACH/direct debit and avoids running proprietary JavaScript within an sfconservancy.org
|
|
||||||
# page. The tradeoff is the slightly visually jarring transition to stripe.com and
|
|
||||||
# back. With relatively little efforte we instead use an embedded Stripe form or Stripe
|
|
||||||
# widgets in our own form; this would just require marginally more code and would run
|
|
||||||
# proprietary JS within the sfconservancy.org page
|
|
||||||
# (https://docs.stripe.com/payments/accept-a-payment). From a sustainer's perspective
|
|
||||||
# it's proprietary JS either way, but it feels conceptually cleaner to isolate
|
|
||||||
# it. Nonetheless this is a slipperly SAAS slope so we need to take care.
|
|
||||||
#
|
|
||||||
# To use Stripe hosted checkout, we first accept the sustainer sign-up and populate our
|
|
||||||
# database with an unpaid order. We then create/register a Stripe checkout session via
|
|
||||||
# the API with with the donor's email, amount and renewal period if relevant and forward
|
|
||||||
# the donor across to the session's unique stripe.com URL.
|
|
||||||
#
|
|
||||||
# The donor pays on stripe.com and is then redirected to a "success_url" along with an
|
|
||||||
# ID parameter we can use to look up the payment details to determine their payment
|
|
||||||
# status. Stripe also allow you to register to accept webhook HTTP requests
|
|
||||||
# corresponding to various events in their system. One of those is
|
|
||||||
# "checkout.session.completed", which corresponds to the redirect to "success_url". The
|
|
||||||
# Stripe fulfillment docs (https://docs.stripe.com/checkout/fulfillment) advise handling
|
|
||||||
# both the "success_url" redirect and the "checkout.session.completed" webhook in case
|
|
||||||
# the redirect fails. If this was a credit card payment, we know then an there that it
|
|
||||||
# was successful. If it's an ACH/direct debit payment, it will take a few days to be
|
|
||||||
# processed and confirmed paid "checkout.session.async_payment_succeeded".
|
|
||||||
#
|
|
||||||
# We record this initial payment success against our order in the sustainer database.
|
|
||||||
#
|
|
||||||
# If auto-renewing, Stripe will transparently set up what they call a "Subscription" and
|
|
||||||
# will automatically bill the donor again next month or year
|
|
||||||
# (https://docs.stripe.com/billing/subscriptions/overview). This is a GOOD THING,
|
|
||||||
# because it avoids us having to worry about missing billing people, or worse,
|
|
||||||
# double-billing. I've been there before. Stripe
|
|
||||||
# offer an additional self-service subscription management portal for donors to use, but
|
|
||||||
# by default they don't communicate with donors directly and leave you to manage
|
|
||||||
# subscriptions manually from within the Stripe dashboard.
|
|
||||||
#
|
|
||||||
# To find out about subscription renewals, we can either batch query the Stripe API or
|
|
||||||
# we can register for webhook events. If using webhooks, there are plenty of subtle
|
|
||||||
# pitfalls such as event ordering, duplication and race conditions that could lead to us
|
|
||||||
# messing up fulfillment. The other challenge with webhooks events is that you need to
|
|
||||||
# link them back to the sustainer order they relate to in our database.
|
|
||||||
|
|
||||||
def sustainers_stripe(request):
|
def sustainers_stripe(request):
|
||||||
|
return render(request, 'supporters/sustainers_stripe.html', {})
|
||||||
|
|
||||||
|
|
||||||
|
def sustainers_stripe2(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = forms.SustainerForm(request.POST)
|
form = forms.SustainerForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
order = form.save()
|
order = form.save(commit=False)
|
||||||
|
order.recurring = form.data['recurring']
|
||||||
|
if order.recurring == 'month':
|
||||||
|
order.amount = form.cleaned_data['amount_monthly']
|
||||||
|
order.save()
|
||||||
base_url = f'{request.scheme}://{request.get_host()}'
|
base_url = f'{request.scheme}://{request.get_host()}'
|
||||||
# There are a few options for integrating with Stripe. A common one, and
|
stripe_checkout_url = create_checkout_session(order.id, order.email, order.amount, order.recurring, base_url)
|
||||||
# possibly the least intrusive is to use the proprietary
|
|
||||||
# https://js.stripe.com/v3/ to embed Stripe form fields into your own
|
|
||||||
# form. Another embeds a hosted form in your page. The approach we've used
|
|
||||||
# is to redirect to a hosted checkout page. This is far from perfect, but it
|
|
||||||
# avoids adding proprietary JS on sfconservancy.org.
|
|
||||||
stripe_checkout_url = create_checkout_session(
|
|
||||||
order.id, order.email, order.amount, order.recurring, base_url
|
|
||||||
)
|
|
||||||
return redirect(stripe_checkout_url)
|
return redirect(stripe_checkout_url)
|
||||||
else:
|
else:
|
||||||
form = forms.SustainerForm()
|
form = forms.SustainerForm()
|
||||||
return render(request, 'supporters/sustainers_stripe.html', {'form': form})
|
return render(request, 'supporters/sustainers_stripe2.html', {'form': form})
|
||||||
|
|
||||||
|
|
||||||
# Use a "restricted" API key and grant access to:
|
stripe.api_key = 'sk_test_zaAqrpHmpkXnHQfAs4UWkE3d'
|
||||||
# - checkout sessions (write)
|
|
||||||
# - credit notes (read) - unclear why, subscription sign-ups fail otherwise
|
|
||||||
stripe.api_key = settings.STRIPE_API_KEY
|
|
||||||
if stripe.api_key == '':
|
|
||||||
logger.warning('Missing STRIPE_API_KEY')
|
|
||||||
|
|
||||||
|
def fulfill_checkout(session_id):
|
||||||
|
print("Fulfilling Checkout Session", session_id)
|
||||||
|
|
||||||
def fulfill_signup(session):
|
# TODO: Make this function safe to run multiple times,
|
||||||
session_id = session["id"]
|
# even concurrently, with the same session ID
|
||||||
logger.debug(f'Fulfilling checkout session {session_id}')
|
|
||||||
|
|
||||||
# TODO: Clean up orders that have been unpaid for, say, 14 days.
|
# TODO: Make sure fulfillment hasn't already been
|
||||||
# TODO: Consider emailing ACH/direct-debit donors immediately to say pending.
|
# peformed for this Checkout Session
|
||||||
|
|
||||||
# Retrieve the Checkout Session from the API with line_items expanded so we can get
|
# Retrieve the Checkout Session from the API with line_items expanded
|
||||||
# the payment intent ID for subscriptions.
|
checkout_session = stripe.checkout.Session.retrieve(
|
||||||
session = stripe.checkout.Session.retrieve(
|
|
||||||
session_id,
|
session_id,
|
||||||
expand=['invoice'],
|
expand=['line_items'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# This ensure's we're looking at a sustainer checkout, not some other
|
|
||||||
# unrelated Stripe checkout.
|
|
||||||
sustainerorder_id = session['client_reference_id']
|
|
||||||
|
|
||||||
# Check the Checkout Session's payment_status property
|
# Check the Checkout Session's payment_status property
|
||||||
# to determine if fulfillment should be peformed
|
# to determine if fulfillment should be peformed
|
||||||
if session.payment_status != 'unpaid':
|
if checkout_session.payment_status != 'unpaid':
|
||||||
logger.debug(f'Actioning paid session {session_id}')
|
# TODO: Perform fulfillment of the line items
|
||||||
|
|
||||||
|
# TODO: Record/save fulfillment status for this
|
||||||
|
# Checkout Session
|
||||||
|
logger.info(f'Session ID {session_id} PAID!')
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
order = SustainerOrder.objects.get(id=checkout_session['client_reference_id'], paid_time=None)
|
||||||
# Lock this order to prevent a race condition from multiple webhooks.
|
order.paid_time=timezone.now()
|
||||||
order = SustainerOrder.objects.filter(id=sustainerorder_id, paid_time=None).select_for_update().get()
|
order.save()
|
||||||
order.stripe_customer_ref = session['customer']
|
logger.info(f'Marked sustainer order {order.id} (order.email) as paid')
|
||||||
order.stripe_subscription_ref = session['subscription']
|
|
||||||
order.stripe_checkout_session_data = session
|
|
||||||
order.stripe_initial_payment_intent_ref = (
|
|
||||||
# One-off sustainer
|
|
||||||
session['payment_intent']
|
|
||||||
# Subscription sustainer
|
|
||||||
or session['invoice']['payment_intent']
|
|
||||||
)
|
|
||||||
order.paid_time = timezone.now()
|
|
||||||
order.save()
|
|
||||||
logger.info(f'Marked sustainer order {order.id} ({order.email}) as paid')
|
|
||||||
payment = SustainerPayment.objects.create(
|
|
||||||
order=order,
|
|
||||||
stripe_invoice_ref=session['invoice']['id'] if session['invoice'] else None,
|
|
||||||
amount=decimal.Decimal(session['amount_total']) / 100,
|
|
||||||
stripe_payment_intent_ref=order.stripe_initial_payment_intent_ref,
|
|
||||||
stripe_invoice_data=session['invoice'],
|
|
||||||
)
|
|
||||||
logger.info(f'Created sustainer payment {payment.id}')
|
|
||||||
email = mail.make_stripe_email(order)
|
|
||||||
email.send()
|
|
||||||
except SustainerOrder.DoesNotExist:
|
except SustainerOrder.DoesNotExist:
|
||||||
logger.info(f'No such unpaid SustainerOrder {sustainerorder_id} - no action')
|
logger.info('No action')
|
||||||
else:
|
|
||||||
logger.debug(f'Unpaid session {session_id} - no action')
|
|
||||||
|
|
||||||
|
|
||||||
def fulfill_invoice_payment(invoice):
|
|
||||||
"""Handle (possible) renewal payment.
|
|
||||||
|
|
||||||
Annoyingly, this handler runs both for initial subscription and renewal payments. A
|
|
||||||
better option would be if there was an event that ran renewal payments ONLY. I
|
|
||||||
looked at "customer.subscription.updated", but couldn't seem to tell whether the
|
|
||||||
update was for a new successful renewal payment as opposed eg. someone changed the
|
|
||||||
subscription amount in the Stripe dashboard.
|
|
||||||
|
|
||||||
Scenarios:
|
|
||||||
|
|
||||||
1. This could be an initial subscription payment or a renewal payment. Only
|
|
||||||
action if payment intent ID doesn't match an initial sign-up payment in our
|
|
||||||
database.
|
|
||||||
|
|
||||||
2. This could also be an initial payment for a subsciption not yet in the
|
|
||||||
database (events came in out of order) or a payment for a non-sustainer
|
|
||||||
subscription. That's fine - we just ignore those cases.
|
|
||||||
"""
|
|
||||||
invoice_id = invoice.id
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
# An alternative to comparing the payment intent reference would be to only
|
|
||||||
# consider orders paid > 28 days ago. Renewals should never happen before then.
|
|
||||||
order = SustainerOrder.objects.exclude(stripe_initial_payment_intent_ref=invoice['payment_intent']).get(
|
|
||||||
stripe_subscription_ref=invoice.subscription, paid_time__isnull=False,
|
|
||||||
)
|
|
||||||
payment = SustainerPayment.objects.create(
|
|
||||||
order=order,
|
|
||||||
stripe_invoice_ref=invoice.id,
|
|
||||||
amount=decimal.Decimal(invoice.total) / 100,
|
|
||||||
stripe_payment_intent_ref=invoice['payment_intent'],
|
|
||||||
stripe_invoice_data=invoice,
|
|
||||||
)
|
|
||||||
logger.info(f'Created sustainer payment {payment.id} for invoice {invoice_id}')
|
|
||||||
except SustainerOrder.DoesNotExist:
|
|
||||||
logger.info(f'No such subscription to renew {invoice.subscription} for invoice {invoice_id}')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def success(request):
|
def success(request):
|
||||||
"""Handle Stripe redirect after successful checkout."""
|
fulfill_checkout(request.GET['session_id'])
|
||||||
# We don't run the fulfillment here since it's unnecessarily complicated to run it
|
|
||||||
# both here and from webhooks.
|
|
||||||
return render(request, 'supporters/stripe_success.html', {})
|
return render(request, 'supporters/stripe_success.html', {})
|
||||||
|
|
||||||
|
|
||||||
def webhook(request):
|
def webhook(request):
|
||||||
"""Handle a request to our webhook endpoint.
|
|
||||||
|
|
||||||
Modelled on https://docs.stripe.com/checkout/fulfillment.
|
|
||||||
|
|
||||||
To test these, either use a service like Pagekite to set up a public link to your
|
|
||||||
development environment and configure webhooks for that, or use the Stripe CLI tool
|
|
||||||
to forward the events to your development environment.
|
|
||||||
"""
|
|
||||||
payload = request.body
|
payload = request.body
|
||||||
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
|
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
|
||||||
event = None
|
event = None
|
||||||
|
|
||||||
# From the "event destinations" page in Stripe's "developer tools" area.
|
# From webhook dashboard
|
||||||
endpoint_secret = settings.STRIPE_ENDPOINT_SECRET
|
endpoint_secret = 'whsec_lLy9pqxAAHdl4fwiC0cFg1KwR6y4CvOH'
|
||||||
if not endpoint_secret:
|
|
||||||
logger.warning('Missing STRIPE_ENDPOINT_SECRET')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
|
event = stripe.Webhook.construct_event(
|
||||||
|
payload, sig_header, endpoint_secret
|
||||||
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Invalid payload
|
# Invalid payload
|
||||||
return HttpResponse(status=400)
|
return HttpResponse(status=400)
|
||||||
|
@ -331,33 +152,10 @@ def webhook(request):
|
||||||
# Invalid signature
|
# Invalid signature
|
||||||
return HttpResponse(status=400)
|
return HttpResponse(status=400)
|
||||||
|
|
||||||
# Register for these webhook events the "event destinations" page. Must be
|
if (
|
||||||
# individually enabled.
|
event['type'] == 'checkout.session.completed'
|
||||||
if event['type'] == 'checkout.session.completed':
|
or event['type'] == 'checkout.session.async_payment_succeeded'
|
||||||
# Successful Stripe checkout. For credit cards, this usually indicates that the
|
):
|
||||||
# payment was successful. For ACH/direct-debit the payment will not yet have
|
fulfill_checkout(event['data']['object']['id'])
|
||||||
# been processed and may take a few days.
|
|
||||||
session = event['data']['object']
|
|
||||||
logger.debug(f'CHECKOUT.SESSION.COMPLETED webhook for session {session["id"]}')
|
|
||||||
fulfill_signup(session)
|
|
||||||
elif event['type'] == 'checkout.session.async_payment_succeeded':
|
|
||||||
# Runs for successful ACH/direct debit payments.
|
|
||||||
session = event['data']['object']
|
|
||||||
logger.debug(f'CHECKOUT.SESSION.ASYNC_PAYMENT_SUCCEEDED webhook for session {session["id"]}')
|
|
||||||
fulfill_signup(session)
|
|
||||||
elif event['type'] == 'invoice.payment_succeeded':
|
|
||||||
# Successful initial subscription or renewal payment (only care about renewals).
|
|
||||||
#
|
|
||||||
# It not clear that this is the *best* webhook or approach to use
|
|
||||||
# handle subscription renewals, but it works.
|
|
||||||
#
|
|
||||||
# You can simulate subscription renewals via the Stripe developers site:
|
|
||||||
# https://docs.stripe.com/billing/testing/test-clocks/simulate-subscriptions
|
|
||||||
#
|
|
||||||
# I found I had to advance time by 1 month first to create the invoice, then 1
|
|
||||||
# day for it to be billed. You can watch all the events via the "stripe listen"
|
|
||||||
# CLI command.
|
|
||||||
invoice = event['data']['object']
|
|
||||||
logger.debug(f'INVOICE.PAYMENT_SUCCEEDED webhook for invoice {invoice["id"]}')
|
|
||||||
fulfill_invoice_payment(invoice)
|
|
||||||
return HttpResponse(status=200)
|
return HttpResponse(status=200)
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/tachyons.min.css' %}"/>
|
<link rel="stylesheet" type="text/css" href="{% static 'css/tachyons.min.css' %}"/>
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="{% static 'css/conservancy.css' %}" />
|
<link rel="stylesheet" type="text/css" media="screen" href="{% static 'css/conservancy.css' %}" />
|
||||||
<link rel="stylesheet" type="text/css" media="(min-width: 67em)" href="{% static 'css/conservancy-bigscreen.css' %}" />
|
<link rel="stylesheet" type="text/css" media="(min-width: 67em)" href="{% static 'css/conservancy-bigscreen.css' %}" />
|
||||||
<script src="{% static 'js/conservancy.js' %}" defer></script>
|
<script type="text/javascript" src="{% static 'js/conservancy.js' %}" defer></script>
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -51,10 +51,10 @@
|
||||||
|
|
||||||
<li class="search dn-ns">
|
<li class="search dn-ns">
|
||||||
<form method="get" action="https://duckduckgo.com" class="ml2 flex mw6">
|
<form method="get" action="https://duckduckgo.com" class="ml2 flex mw6">
|
||||||
<input id="search-query" type="text" name="q" placeholder="Search with DuckDuckGo" class="pa2 ba b--gray br0" style="flex: 1 1 auto; width: 1%;" />
|
<input id="search-query" type="text" name="q" placeholder="Search with DuckDuckGo" class="pa2 ba b--gray br0" style="x-border-right: none; flex: 1 1 auto; width: 1%;" />
|
||||||
<input type="hidden" name="sites" value="sfconservancy.org" />
|
<input type="hidden" name="sites" value="sfconservancy.org" />
|
||||||
<button type="submit" class="bg-orange bn white pa2 pointer btn-orange" style="margin-left: -1px;">
|
<button type="submit" class="bg-orange bn white pa2 pointer btn-orange" style="margin-left: -1px;">
|
||||||
<svg style="color: white; width: 20px; height: 20px;"><use href="{% static 'img/font_awesome.svg' %}#search"></use></svg>
|
<svg style="color: white; width: 20px; height: 20px;"><use href="{% static 'img/font_awesome.svg' %}#search"></use></svg></a>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
<li class="Sustainers"><a href="/sustainer/">Sustainers</a></li>
|
<li class="Sustainers"><a href="/sustainer/">Sustainers</a></li>
|
||||||
<li class="Directors"><a href="/about/board/">Board of Directors</a></li>
|
<li class="Directors"><a href="/about/board/">Board of Directors</a></li>
|
||||||
<li class="Staff"><a href="/about/staff/">Staff</a></li>
|
<li class="Staff"><a href="/about/staff/">Staff</a></li>
|
||||||
<li class="Eval"><a href="/about/eval-committee/">Evaluation Committee</a></li>
|
<li clas="Eval"><a href="/about/eval-committee/">Evaluation Committee</a></li>
|
||||||
<li class="Outside"><a href="/about/outside/">Outside Counsel, et alia</a></li>
|
<li clas="Outside"><a href="/about/outside/">Outside Counsel, et alia</a></li>
|
||||||
<li class="Transparency"><a href="/about/transparency">Transparency</a></li>
|
<li class="Transparency"><a href="/about/transparency">Transparency</a></li>
|
||||||
<li class="Contact"><a href="/about/contact/">Contact</a></li>
|
<li class="Contact"><a href="/about/contact/">Contact</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% block category %}sustainer{% endblock %}
|
{% block category %}sustainer{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<script src="{% static 'js/supporter-page.js' %}" defer></script>
|
<script type="text/javascript" src="{% static 'js/supporter-page.js' %}" defer></script>
|
||||||
<link href="{% static 'css/forms.css' %}" rel="stylesheet" type="text/css"/>
|
<link href="{% static 'css/forms.css' %}" rel="stylesheet" type="text/css"/>
|
||||||
<style>
|
<style>
|
||||||
.hidden { display: none; }
|
.hidden { display: none; }
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
<span id="form-correction-needed" class="form-error">Please ensure all form data above is correct.</span>
|
<span id="form-correction-needed" class="form-error">Please ensure all form data above is correct.</span>
|
||||||
|
|
||||||
<div style="overflow: auto; text-align: center;">
|
<div style="overflow: auto; text-align: center;">
|
||||||
<a href="/img/tshirt-2024.png"><img src="{% static 'img/tshirt-2024.png' %}" height="300"/></a>
|
<a href="/img/tshirt-2023.png"><img src="{% static 'img/tshirt-2023.png' %}" height="300"/></a>
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ urlpatterns = [
|
||||||
re_path(r'^about/', views.content),
|
re_path(r'^about/', views.content),
|
||||||
re_path(r'^activities/', views.content),
|
re_path(r'^activities/', views.content),
|
||||||
re_path(r'^copyleft-compliance/', views.content, {'fundraiser_sought': 'vmware-match-0'}),
|
re_path(r'^copyleft-compliance/', views.content, {'fundraiser_sought': 'vmware-match-0'}),
|
||||||
path('donate/', views.content, name='donate'),
|
re_path(r'^donate/', views.content),
|
||||||
path('fossy/', views.content),
|
path('fossy/', views.content),
|
||||||
re_path(r'^GiveUpGitHub/', views.content),
|
re_path(r'^GiveUpGitHub/', views.content),
|
||||||
re_path(r'^learn/', views.content),
|
re_path(r'^learn/', views.content),
|
||||||
|
@ -61,8 +61,6 @@ urlpatterns = [
|
||||||
re_path(r'^privacy-policy/', views.content),
|
re_path(r'^privacy-policy/', views.content),
|
||||||
re_path(r'^projects/', views.content),
|
re_path(r'^projects/', views.content),
|
||||||
re_path(r'^sustainer/', views.content),
|
re_path(r'^sustainer/', views.content),
|
||||||
|
|
||||||
path('captcha/', include('captcha.urls')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Serve uploaded media. Works only when DEBUG == True. Using '/media/'
|
# Serve uploaded media. Works only when DEBUG == True. Using '/media/'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .emails import make_candidate_email
|
from .emails import make_candidate_email
|
||||||
from .models import Candidate, Comment, SourceOffer
|
from .models import Candidate, Comment
|
||||||
|
|
||||||
|
|
||||||
class CommentInline(admin.TabularInline):
|
class CommentInline(admin.TabularInline):
|
||||||
|
@ -36,10 +36,3 @@ class CandidateAdmin(admin.ModelAdmin):
|
||||||
# Announce the new candidate
|
# Announce the new candidate
|
||||||
email = make_candidate_email(obj, request.user)
|
email = make_candidate_email(obj, request.user)
|
||||||
email.send()
|
email.send()
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SourceOffer)
|
|
||||||
class SourceOfferAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['time', 'vendor', 'device']
|
|
||||||
fields = ['time', 'vendor', 'device', 'photo']
|
|
||||||
readonly_fields = ['time']
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .models import Comment, SourceOffer
|
from .models import Comment
|
||||||
|
|
||||||
|
|
||||||
class CommentForm(forms.ModelForm):
|
class CommentForm(forms.ModelForm):
|
||||||
|
@ -17,14 +17,3 @@ class CommentForm(forms.ModelForm):
|
||||||
|
|
||||||
class DownloadForm(forms.Form):
|
class DownloadForm(forms.Form):
|
||||||
agree = forms.BooleanField(label="I understand that the goal of this process is to determine compliance with FOSS licenses, and that in downloading the source code candidate and/or firmware image, I am assisting SFC as a volunteer to investigate that question. I, therefore, promise and represent that I will not copy, distribute, modify, or otherwise use this source code candidate and/or firmware image for any purpose other than to help SFC evaluate the source code candidate for compliance with the terms of FOSS licenses, including but not limited to any version of the GNU General Public License. Naturally, if I determine in good faith that portions of the source code candidate and/or firmware image are subject to a FOSS license and are compliant with it, I may copy, distribute, modify, or otherwise use those portions in accordance with the FOSS license, and I take full responsibility for that determination and subsequent use.")
|
agree = forms.BooleanField(label="I understand that the goal of this process is to determine compliance with FOSS licenses, and that in downloading the source code candidate and/or firmware image, I am assisting SFC as a volunteer to investigate that question. I, therefore, promise and represent that I will not copy, distribute, modify, or otherwise use this source code candidate and/or firmware image for any purpose other than to help SFC evaluate the source code candidate for compliance with the terms of FOSS licenses, including but not limited to any version of the GNU General Public License. Naturally, if I determine in good faith that portions of the source code candidate and/or firmware image are subject to a FOSS license and are compliant with it, I may copy, distribute, modify, or otherwise use those portions in accordance with the FOSS license, and I take full responsibility for that determination and subsequent use.")
|
||||||
|
|
||||||
|
|
||||||
class SourceOfferForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = SourceOffer
|
|
||||||
fields = ['vendor', 'device', 'photo']
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['photo'].widget.attrs['capture'] = 'camera'
|
|
||||||
self.fields['photo'].widget.attrs['accept'] = 'image/*'
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Generated by Django 4.2.11 on 2024-07-22 08:59
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('usethesource', '0008_comment_attribute_to'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SourceOffer',
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
'id',
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name='ID',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
('vendor', models.CharField(max_length=50, verbose_name='Vendor name')),
|
|
||||||
('device', models.CharField(max_length=50, verbose_name='Device name')),
|
|
||||||
('photo', models.ImageField(upload_to='usethesource/offers')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 4.2.11 on 2024-07-29 09:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('usethesource', '0009_sourceoffer'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sourceoffer',
|
|
||||||
name='time',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -67,13 +67,3 @@ class Comment(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['id']
|
ordering = ['id']
|
||||||
|
|
||||||
|
|
||||||
class SourceOffer(models.Model):
|
|
||||||
time = models.DateTimeField(auto_now_add=True, null=True)
|
|
||||||
vendor = models.CharField('Vendor name', max_length=50)
|
|
||||||
device = models.CharField('Device name', max_length=50)
|
|
||||||
photo = models.ImageField(upload_to='usethesource/offers')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.vendor} {self.device}'
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<p>One crucial way to get involved is to let us know about any source candidates you find! Many devices have an offer for source code (check the manual or device's user interface to find it) and we'd be very interested to know what they send you when you request it. Here are the steps to submit a new source candidate to list on this page:</p>
|
<p>One crucial way to get involved is to let us know about any source candidates you find! Many devices have an offer for source code (check the manual or device's user interface to find it) and we'd be very interested to know what they send you when you request it. Here are the steps to submit a new source candidate to list on this page:</p>
|
||||||
|
|
||||||
<ol class="pl4">
|
<ol class="pl4">
|
||||||
<li class="mb2">find a source candidate offered by a company - normally this is offered to you in the manual or user interface of your device, through a link or email address (the company's GitHub page is not canonical, unless they explicitly say so in this offer). If you're curious what an offer is, check out the PDFs referenced in <a href="https://sfconservancy.org/blog/2022/dec/21/energyguide-software-repair-label/">our submission to the FTC</a>, and <a href="{% url 'usethesource:upload_offer' %}">submit a picture/image of a new offer</a> so we can test it for you if you like</li>
|
<li class="mb2">find a source candidate offered by a company - normally this is offered to you in the manual or user interface of your device, through a link or email address (the company's GitHub page is not canonical, unless they explicitly say so in this offer)</li>
|
||||||
|
|
||||||
<li class="mb2"><a href="https://usl-upload.sfconservancy.org/s/4Ykmx7rSGMJ7s43">upload the source candidate</a> to us - write down the file name(s) you uploaded for the next step (can be multiple), and upload a firmware image if you have it and are ok with us publishing it</li>
|
<li class="mb2"><a href="https://usl-upload.sfconservancy.org/s/4Ykmx7rSGMJ7s43">upload the source candidate</a> to us - write down the file name(s) you uploaded for the next step (can be multiple), and upload a firmware image if you have it and are ok with us publishing it</li>
|
||||||
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
{% extends "usethesource/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Upload an offer for source - Software Freedom Conservancy{% endblock %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
{{ block.super }}
|
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
<section class="mt4 mb3">
|
|
||||||
<h2 class="f2 lh-title ttu mt0">Upload an offer for source</h2>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<form id="form" hx-encoding="multipart/form-data" hx-post="{% url 'usethesource:upload_offer' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
<div class="mv2">
|
|
||||||
{{ form.vendor.errors }}
|
|
||||||
<label for="{{ form.vendor.id_for_label }}" class="db mb1">Vendor:</label>
|
|
||||||
{{ form.vendor }}
|
|
||||||
</div>
|
|
||||||
<div class="mv2">
|
|
||||||
{{ form.device.errors }}
|
|
||||||
<label for="{{ form.device.id_for_label }}" class="db mb1">Device:</label>
|
|
||||||
{{ form.device }}
|
|
||||||
</div>
|
|
||||||
<div class="mv2">
|
|
||||||
{{ form.photo.errors }}
|
|
||||||
<label for="{{ form.photo.id_for_label }}" class="db mb1">Photo:</label>
|
|
||||||
{{ form.photo }}
|
|
||||||
</div>
|
|
||||||
<progress id="progress" class="htmx-indicator" value="0" max="100"></progress>
|
|
||||||
<div class="mv1">
|
|
||||||
<button type="submit" class="white bg-green b db pv2 ph3 bn mb2">Send</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
form = document.querySelector('#form');
|
|
||||||
let progress = document.querySelector('#progress');
|
|
||||||
form.addEventListener('htmx:xhr:progress', function(evt) {
|
|
||||||
console.log('progress', evt.detail.loaded/evt.detail.total * 100);
|
|
||||||
progress.value = evt.detail.loaded/evt.detail.total * 100;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock content %}
|
|
|
@ -1 +0,0 @@
|
||||||
<p>Thanks! We've received your offer for source.</p>
|
|
|
@ -13,5 +13,4 @@ urlpatterns = [
|
||||||
path('delete-comment/<int:comment_id>/<show_add>/', views.delete_comment, name='delete_comment'),
|
path('delete-comment/<int:comment_id>/<show_add>/', views.delete_comment, name='delete_comment'),
|
||||||
path('add-button/<slug:slug>/', views.add_button, name='add_button'),
|
path('add-button/<slug:slug>/', views.add_button, name='add_button'),
|
||||||
path('ccirt-process/', views.ccirt_process, name='ccirt_process'),
|
path('ccirt-process/', views.ccirt_process, name='ccirt_process'),
|
||||||
path('offer/', views.upload_offer, name='upload_offer'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.core.exceptions import PermissionDenied
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
|
||||||
from .models import Candidate, Comment
|
from .models import Candidate, Comment
|
||||||
from .forms import CommentForm, DownloadForm, SourceOfferForm
|
from .forms import CommentForm, DownloadForm
|
||||||
from .emails import make_comment_email
|
from .emails import make_comment_email
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,21 +91,3 @@ def add_button(request, slug):
|
||||||
|
|
||||||
def ccirt_process(request):
|
def ccirt_process(request):
|
||||||
return render(request, 'usethesource/ccirt_process.html', {})
|
return render(request, 'usethesource/ccirt_process.html', {})
|
||||||
|
|
||||||
|
|
||||||
def handle_uploaded_file(f):
|
|
||||||
with open("some/file/name.txt", "wb+") as destination:
|
|
||||||
for chunk in f.chunks():
|
|
||||||
destination.write(chunk)
|
|
||||||
|
|
||||||
def upload_offer(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = SourceOfferForm(request.POST, request.FILES)
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
return render(request, 'usethesource/upload_success_partial.html')
|
|
||||||
else:
|
|
||||||
return render(request, 'usethesource/upload_offer.html', {'form': form})
|
|
||||||
else:
|
|
||||||
form = SourceOfferForm()
|
|
||||||
return render(request, 'usethesource/upload_offer.html', {'form': form})
|
|
||||||
|
|
|
@ -130,10 +130,6 @@
|
||||||
apt:
|
apt:
|
||||||
name: build-essential,python3-dev,libffi-dev
|
name: build-essential,python3-dev,libffi-dev
|
||||||
|
|
||||||
- name: Install flite and sox for CAPTCHA text-to-speech
|
|
||||||
apt:
|
|
||||||
name: flite, sox
|
|
||||||
|
|
||||||
- name: Security settings
|
- name: Security settings
|
||||||
apt:
|
apt:
|
||||||
name: fail2ban
|
name: fail2ban
|
||||||
|
@ -161,7 +157,7 @@
|
||||||
# TODO: Needs to force owner to www-data:www-data
|
# TODO: Needs to force owner to www-data:www-data
|
||||||
- name: Git checkout
|
- name: Git checkout
|
||||||
ansible.builtin.git:
|
ansible.builtin.git:
|
||||||
repo: 'https://f.sfconservancy.org/Conservancy/website'
|
repo: 'https://k.sfconservancy.org/website'
|
||||||
dest: /var/www/website
|
dest: /var/www/website
|
||||||
version: master
|
version: master
|
||||||
remote: upstream
|
remote: upstream
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
# Installed in virtualenv
|
# Installed in virtualenv
|
||||||
Django==5.1.2
|
Django==4.2.11
|
||||||
django-countries==7.6.1
|
|
||||||
stripe
|
|
||||||
# Provided by Debian Bookworm.
|
# Provided by Debian Bookworm.
|
||||||
beautifulsoup4==4.11.2
|
beautifulsoup4==4.11.2
|
||||||
html5lib==1.1
|
html5lib==1.1
|
||||||
|
django-countries==7.3.2
|
||||||
Pillow==9.4.0
|
Pillow==9.4.0
|
||||||
django-simple-captcha==0.6.0
|
stripe
|
Loading…
Reference in a new issue