Merge branch 'master' into sustainer-payment

This commit is contained in:
Ben Sturmfels 2024-10-08 23:55:14 +11:00
commit 36ecf098b0
Signed by: bsturmfels
GPG key ID: 023C05E2C9C068F0
14 changed files with 170 additions and 13 deletions

View file

@ -49,10 +49,20 @@ 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></ul></li> Motion for Summary Adjudication</a></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>

View file

@ -123,12 +123,20 @@ 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/'

View file

@ -76,12 +76,6 @@ 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%;
} }

View file

@ -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 from .models import Candidate, Comment, SourceOffer
class CommentInline(admin.TabularInline): class CommentInline(admin.TabularInline):
@ -36,3 +36,10 @@ 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']

View file

@ -1,6 +1,6 @@
from django import forms from django import forms
from .models import Comment from .models import Comment, SourceOffer
class CommentForm(forms.ModelForm): class CommentForm(forms.ModelForm):
@ -17,3 +17,14 @@ 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/*'

View file

@ -0,0 +1,30 @@
# 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')),
],
),
]

View file

@ -0,0 +1,18 @@
# 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),
),
]

View file

@ -67,3 +67,13 @@ 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}'

View file

@ -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)</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). 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"><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>

View file

@ -0,0 +1,49 @@
{% 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 %}

View file

@ -0,0 +1 @@
<p>Thanks! We've received your offer for source.</p>

View file

@ -13,4 +13,5 @@ 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'),
] ]

View file

@ -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 from .forms import CommentForm, DownloadForm, SourceOfferForm
from .emails import make_comment_email from .emails import make_comment_email
@ -91,3 +91,21 @@ 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})

View file

@ -1,5 +1,5 @@
# Installed in virtualenv # Installed in virtualenv
Django==4.2.11 Django==4.2.16
# Provided by Debian Bookworm. # Provided by Debian Bookworm.
beautifulsoup4==4.11.2 beautifulsoup4==4.11.2
html5lib==1.1 html5lib==1.1