Changeset - 36ecf098b059
conservancy/content/copyleft-compliance/vizio.html
Show inline comments
...
 
@@ -40,23 +40,33 @@ Original Complaint (2021-10-19)</li>
 
  <li><a href="https://storage.courtlistener.com/recap/gov.uscourts.cacd.837808/gov.uscourts.cacd.837808.30.0.pdf"><strong>Decision by the federal court to remand the case to state court</strong></a></li>
 
</ul></li>
 

	
 
<li><h5>Vizio's Motion for Summary Judgment</h5>
 
<ul>
 
  <li><a href="https://sfconservancy.org/docs/2023-4-28_VIZIOs_Motion_for_Summary_Judgment_with_Reservation.pdf">Vizio's Motion for Summary Judgment</li>
 
  <li><a href="https://sfconservancy.org/docs/SFC_response_to_summary_judgement.pdf">SFC's response to Vizio's Motion for Summary Judgment</li>
 
  <li><a href="https://sfconservancy.org/docs/Vizio_summary_judgement_reply_brief.pdf">Vizio's reply to SFC's response to Vizio's Motion for Summary Judgment</a></li>
 
  <li><a href="https://sfconservancy.org/docs/Transcript_Full_Vizios_MSJ_HearingDeptC-33.231005.pdf">Full transcript from the hearing</a></li>
 
  <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>
 
</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>
 
<ul>
 
  <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>
 

	
 
<h3>MEDIA CONTACT</h3>
 

	
 
You can reach out media team at <a href="mailto:media@sfconservancy.org">&lt;media@sfconservancy.org&gt;</a></p>
 

	
 
{% endblock %}
conservancy/settings/base.py
Show inline comments
...
 
@@ -114,30 +114,38 @@ TEMPLATES = [
 
                'conservancy.context_processors.host_url',
 
                'conservancy.context_processors.sitefundraiser',
 
            ]
 
        }
 
    }
 
]
 

	
 
# Internationalization
 
TIME_ZONE = 'America/New_York'
 
LANGUAGE_CODE = 'en-us'
 
USE_TZ = False
 

	
 
STORAGES = {
 
    'default': {
 
        'BACKEND': 'django.core.files.storage.FileSystemStorage',
 
    },
 
    'staticfiles': {
 
        'BACKEND': 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage',
 
    },
 
}
 

	
 
STATIC_URL = '/static/'
 
STATIC_ROOT = BASE_DIR.parent / 'collected_static'
 
STATICFILES_DIRS = [
 
    BASE_DIR / 'static',
 
]
 
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
 

	
 
MEDIA_ROOT = BASE_DIR.parent / 'media'
 
MEDIA_URL = '/media/'
 

	
 
MIDDLEWARE = [
 
    'django.middleware.common.CommonMiddleware',
 
    'django.contrib.sessions.middleware.SessionMiddleware',
 
    'django.contrib.auth.middleware.AuthenticationMiddleware',
 
    'django.contrib.messages.middleware.MessageMiddleware',
 
]
 

	
 
USETHESOURCE = {
conservancy/static/css/conservancy.css
Show inline comments
...
 
@@ -67,30 +67,24 @@ form[action$="#fixme"]:before {
 
  font-size: 0.6rem;
 
  color: var(--orange);
 
  position: absolute;
 
  top: -4px;
 
  right: -4px;
 
  padding: 1px;
 
  background: yellow;
 
  /* opacity: 0.8; */
 
  width: 12px;
 
  text-align: center;
 
}
 

	
 
input:focus {
 
  z-index: 3;
 
  border-color: #86b7fe;
 
  box-shadow: 0 0 0 .25rem rgb(236, 99, 67, .5);
 
}
 

	
 
video {
 
    max-width: 100%;
 
}
 

	
 
a.read-more {
 
    cursor: pointer;
 
    font-style: italic;
 
}
 

	
 
.btn-orange {
 
  color: white;
 
  background: var(--orange);
conservancy/usethesource/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
from .emails import make_candidate_email
 
from .models import Candidate, Comment
 
from .models import Candidate, Comment, SourceOffer
 

	
 

	
 
class CommentInline(admin.TabularInline):
 
    model = Comment
 
    fields = ['user', 'message']
 
    extra = 0
 

	
 

	
 
@admin.register(Candidate)
 
class CandidateAdmin(admin.ModelAdmin):
 
    list_display = ['name', 'vendor', 'device', 'release_date', 'ordering']
 
    list_editable = ['ordering']
...
 
@@ -27,12 +27,19 @@ class CandidateAdmin(admin.ModelAdmin):
 
    ]
 
    inlines = [CommentInline]
 
    prepopulated_fields = {'slug': ['name']}
 
    view_on_site = True
 

	
 
    def save_model(self, request, obj, form, change):
 
        send_email = obj.id is None
 
        super().save_model(request, obj, form, change)
 
        if send_email:
 
            # Announce the new candidate
 
            email = make_candidate_email(obj, request.user)
 
            email.send()
 

	
 

	
 
@admin.register(SourceOffer)
 
class SourceOfferAdmin(admin.ModelAdmin):
 
    list_display = ['time', 'vendor', 'device']
 
    fields = ['time', 'vendor', 'device', 'photo']
 
    readonly_fields = ['time']
conservancy/usethesource/forms.py
Show inline comments
 
from django import forms
 

	
 
from .models import Comment
 
from .models import Comment, SourceOffer
 

	
 

	
 
class CommentForm(forms.ModelForm):
 
    post_to_list = forms.BooleanField(required=False)
 

	
 
    class Meta:
 
        model = Comment
 
        fields = ['time', 'attribute_to', 'message', 'post_to_list']
 

	
 
    def __init__(self, *args, **kwargs):
 
        super().__init__(*args, **kwargs)
 
        self.fields['time'].widget.input_type = 'datetime-local'
 

	
 

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

	
 

	
 
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/*'
conservancy/usethesource/migrations/0009_sourceoffer.py
Show inline comments
 
new file 100644
 
# 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')),
 
            ],
 
        ),
 
    ]
conservancy/usethesource/migrations/0010_sourceoffer_time.py
Show inline comments
 
new file 100644
 
# 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),
 
        ),
 
    ]
conservancy/usethesource/models.py
Show inline comments
...
 
@@ -58,12 +58,22 @@ class Comment(models.Model):
 
    def in_reply_to(self):
 
        """Determine the message_id of the previous comment or the candidate.
 

	
 
        Used for email threading.
 
        """
 
        if prev_comment := self._find_previous_comment():
 
            return prev_comment.email_message_id
 
        else:
 
            return self.candidate.email_message_id
 

	
 
    class Meta:
 
        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}'
conservancy/usethesource/templates/usethesource/landing_page.html
Show inline comments
...
 
@@ -15,25 +15,25 @@
 
        <span style="flex-shrink: 1">Submit a candidate</span>
 
        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 5 100 100" xml:space="preserve" style="width: 90px; vertical-align: middle" fill="currentColor"><g><path d="M49.67,65.06c1.1,0,2-0.9,2-2V27.28l12.47,12.47c0.39,0.39,0.9,0.59,1.41,0.59s1.02-0.2,1.41-0.59   c0.78-0.78,0.78-2.05,0-2.83L51.08,21.03c-0.38-0.38-0.88-0.59-1.41-0.59s-1.04,0.21-1.41,0.59L32.37,36.92   c-0.78,0.78-0.78,2.05,0,2.83c0.78,0.78,2.05,0.78,2.83,0l12.47-12.47v35.79C47.67,64.17,48.56,65.06,49.67,65.06z"/><path d="M73.66,47.77H61.23c-1.1,0-2,0.9-2,2s0.9,2,2,2h12.43c1.21,0,2.19,0.98,2.19,2.19v19.41c0,1.21-0.98,2.19-2.19,2.19H26.34   c-1.21,0-2.19-0.98-2.19-2.19V53.96c0-1.21,0.98-2.19,2.19-2.19H40.1c1.1,0,2-0.9,2-2s-0.9-2-2-2H26.34   c-3.41,0-6.19,2.78-6.19,6.19v19.41c0,3.41,2.78,6.19,6.19,6.19h47.33c3.41,0,6.19-2.78,6.19-6.19V53.96   C79.85,50.54,77.08,47.77,73.66,47.77z"/></g></svg>
 
      </a>
 
    </div>
 

	
 
    <p>Software Freedom Conservancy works for your right to repair and modify the software on your devices. <strong>Use The Source</strong> shows you how we evaluate the source code candidates  companies must provide for GPLed software. Join us as we highlight common issues in source candidates, and what companies need to do to fix them.  Check out the options below, or subscribe to our <a href="https://lists.sfconservancy.org/mailman/listinfo/ccs-review">mailing list</a> to participate in the public discussion on these candidates.</p>
 
  </section>
 

	
 
  <h2 id="submit-a-candidate" class="f2 lh-title ttu mt0 mb2">Submit a Candidate</h2>
 
  <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">
 
    <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">email us at <a href="mailto:compliance@sfconservancy.org">compliance@sfconservancy.org</a> with the following details:
 

	
 
      <div class="bg-black-10 mt2 pv2 ph3">
 
        <code>
 
        Subject: candidate to add: [brand/model]<br><br>
 

	
 
        manufacturer/brand of device:<br>
 
        model number of device:<br>
 
        version number of software on device (if applicable):<br>
conservancy/usethesource/templates/usethesource/upload_offer.html
Show inline comments
 
new file 100644
 
{% 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 %}
conservancy/usethesource/templates/usethesource/upload_success_partial.html
Show inline comments
 
new file 100644
 
<p>Thanks! We've received your offer for source.</p>
conservancy/usethesource/urls.py
Show inline comments
...
 
@@ -4,13 +4,14 @@ from . import views
 

	
 
app_name = 'usethesource'
 
urlpatterns = [
 
    path('', views.landing_page, name='landing'),
 
    path('candidate/<slug:slug>/', views.candidate_page, name='candidate'),
 
    path('download/<slug:slug>/<download_type>/', views.download_page, name='download'),
 
    path('add-comment/<slug:slug>/', views.create_comment, name='add_comment'),
 
    path('edit-comment/<int:comment_id>/', views.edit_comment, name='edit_comment'),
 
    path('comment/<int:comment_id>/<show_add>/', views.view_comment, name='view_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('ccirt-process/', views.ccirt_process, name='ccirt_process'),
 
    path('offer/', views.upload_offer, name='upload_offer'),
 
]
conservancy/usethesource/views.py
Show inline comments
 
from django.contrib.admin.views.decorators import staff_member_required
 
from django.core.exceptions import PermissionDenied
 
from django.shortcuts import get_object_or_404, redirect, render
 

	
 
from .models import Candidate, Comment
 
from .forms import CommentForm, DownloadForm
 
from .forms import CommentForm, DownloadForm, SourceOfferForm
 
from .emails import make_comment_email
 

	
 

	
 
def landing_page(request):
 
    candidates = Candidate.objects.all()
 
    return render(request, 'usethesource/landing_page.html', {'candidates': candidates})
 

	
 

	
 
def candidate_page(request, slug):
 
    candidate = get_object_or_404(Candidate, slug=slug)
 
    return render(request, 'usethesource/candidate.html', {'candidate': candidate, 'add': True})
 

	
...
 
@@ -82,12 +82,30 @@ def delete_comment(request, comment_id, show_add):
 
    show_add = show_add == 'true'
 
    return render(request, 'usethesource/comment_deleted.html', {'comment': None, 'add': show_add})
 

	
 

	
 
@staff_member_required
 
def add_button(request, slug):
 
    candidate = get_object_or_404(Candidate, slug=slug)
 
    return render(request, 'usethesource/add_comment_button_partial.html', {'candidate': candidate})
 

	
 

	
 
def ccirt_process(request):
 
    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})
requirements.txt
Show inline comments
 
# Installed in virtualenv
 
Django==4.2.11
 
Django==4.2.16
 
# Provided by Debian Bookworm.
 
beautifulsoup4==4.11.2
 
html5lib==1.1
 
django-countries==7.3.2
 
Pillow==9.4.0
 
stripe
...
 
\ No newline at end of file
0 comments (0 inline, 0 general)