From 6e2fda8b014ad8667226061edf79f9fb9b5b558a Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 30 Nov 2021 16:15:59 +1100 Subject: [PATCH 01/16] Prototype copyright assignment form. --- www/conservancy/apps/assignment/__init__.py | 0 www/conservancy/apps/assignment/apps.py | 7 +++++ .../apps/assignment/migrations/__init__.py | 0 www/conservancy/apps/assignment/models.py | 28 +++++++++++++++++++ www/conservancy/apps/assignment/urls.py | 8 ++++++ www/conservancy/apps/assignment/views.py | 18 ++++++++++++ www/conservancy/urls.py | 1 + 7 files changed, 62 insertions(+) create mode 100644 www/conservancy/apps/assignment/__init__.py create mode 100644 www/conservancy/apps/assignment/apps.py create mode 100644 www/conservancy/apps/assignment/migrations/__init__.py create mode 100644 www/conservancy/apps/assignment/models.py create mode 100644 www/conservancy/apps/assignment/urls.py create mode 100644 www/conservancy/apps/assignment/views.py diff --git a/www/conservancy/apps/assignment/__init__.py b/www/conservancy/apps/assignment/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/www/conservancy/apps/assignment/apps.py b/www/conservancy/apps/assignment/apps.py new file mode 100644 index 00000000..9fe186c5 --- /dev/null +++ b/www/conservancy/apps/assignment/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class AssignmentConfig(AppConfig): + name = 'assignment' diff --git a/www/conservancy/apps/assignment/migrations/__init__.py b/www/conservancy/apps/assignment/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/www/conservancy/apps/assignment/models.py b/www/conservancy/apps/assignment/models.py new file mode 100644 index 00000000..0b8243a6 --- /dev/null +++ b/www/conservancy/apps/assignment/models.py @@ -0,0 +1,28 @@ +from __future__ import unicode_literals + +from django.db import models + + +class Assignment(models.Model): + full_name = models.CharField(max_length=255) + email = models.EmailField() + place_of_residence = models.TextField( + 'Country of citizenship or residential address', + blank=True) + + repository = models.URLField( + 'Code repository', + blank=True, + ) + coverage = models.CharField( + verbose_name='Time period to assign', + max_length=50, + choices=[ + ('up to this year', 'One-off up to and including this year'), + ('ongoing', 'All existing and new contributions'), + ], + default='up to this year', + ) + attestation_of_copyright = models.BooleanField( + 'I attest that I own the copyright on these works' + ) diff --git a/www/conservancy/apps/assignment/urls.py b/www/conservancy/apps/assignment/urls.py new file mode 100644 index 00000000..435f6b68 --- /dev/null +++ b/www/conservancy/apps/assignment/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url + +from .views import AssignmentCreateView + + +urlpatterns = [ + url(r'add/', AssignmentCreateView.as_view(), name='assignement-add'), +] diff --git a/www/conservancy/apps/assignment/views.py b/www/conservancy/apps/assignment/views.py new file mode 100644 index 00000000..73aac1d4 --- /dev/null +++ b/www/conservancy/apps/assignment/views.py @@ -0,0 +1,18 @@ +from django.shortcuts import render + + +from django.urls import reverse_lazy +from django.views.generic.edit import CreateView, DeleteView, UpdateView +from .models import Assignment + + +class AssignmentCreateView(CreateView): + model = Assignment + fields = [ + 'full_name', + 'email', + 'place_of_residence', + 'repository', + 'coverage', + 'attestation_of_copyright', + ] diff --git a/www/conservancy/urls.py b/www/conservancy/urls.py index 0cd95c48..5c07d2c1 100644 --- a/www/conservancy/urls.py +++ b/www/conservancy/urls.py @@ -59,4 +59,5 @@ urlpatterns = [ url(r'^coming-soon.html', static_views.index), url(r'^fundraiser_data', fundgoal_views.view), url(r'^ccs-upload/', include('conservancy.apps.ccs_upload.urls', namespace='ccs_upload')), + url(r'^assignment', include('conservancy.apps.assignment.urls')), ] From 030ce8d60ce0916cc9e4389306916d17b2e29d77 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 30 Nov 2021 16:18:43 +1100 Subject: [PATCH 02/16] Add assignment template. --- .../templates/assignment/assignment_form.html | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 www/conservancy/templates/assignment/assignment_form.html diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html new file mode 100644 index 00000000..ed2a07a9 --- /dev/null +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -0,0 +1,38 @@ +{% extends "base_conservancy.html" %} +{% block category %}Copyright Assignment{% endblock %} +{% block outercontent %} + +

Copyright Assignment

+ +
+

Thank you for considering assigning your copyright to the Software Freedom Conservancy. Your assignment helps us enforce copyright on your behalf.

+ +

Please complete the following form and we will prepare the appropriate paperwork. You will receive a PDF form to sign and be witnessed by a public notary.

+
+ +
+ {% csrf_token %} + {{ form.as_p }} + +

Please be aware that some employment agreements explicitly transfer copyright ownership to the employer. We recommend you review your recent employment agreements for such clauses.

+ +

+

+{% endblock %} From 6a27ad74fe0601f83f25054674e25076487bc721 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 30 Nov 2021 16:34:26 +1100 Subject: [PATCH 03/16] Add assignment thanks page. --- www/conservancy/apps/assignment/urls.py | 5 ++-- www/conservancy/apps/assignment/views.py | 9 +++++-- .../templates/assignment/thanks.html | 27 +++++++++++++++++++ www/conservancy/urls.py | 2 +- 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 www/conservancy/templates/assignment/thanks.html diff --git a/www/conservancy/apps/assignment/urls.py b/www/conservancy/apps/assignment/urls.py index 435f6b68..1be0b775 100644 --- a/www/conservancy/apps/assignment/urls.py +++ b/www/conservancy/apps/assignment/urls.py @@ -1,8 +1,9 @@ from django.conf.urls import url -from .views import AssignmentCreateView +from .views import AssignmentCreateView, AssignmentThanksView urlpatterns = [ - url(r'add/', AssignmentCreateView.as_view(), name='assignement-add'), + url(r'^$', AssignmentCreateView.as_view(), name='assignement-add'), + url(r'^thanks/$', AssignmentThanksView.as_view(), name='assignment-thanks'), ] diff --git a/www/conservancy/apps/assignment/views.py b/www/conservancy/apps/assignment/views.py index 73aac1d4..b2475d06 100644 --- a/www/conservancy/apps/assignment/views.py +++ b/www/conservancy/apps/assignment/views.py @@ -1,8 +1,8 @@ from django.shortcuts import render - from django.urls import reverse_lazy -from django.views.generic.edit import CreateView, DeleteView, UpdateView +from django.views.generic import TemplateView +from django.views.generic.edit import CreateView from .models import Assignment @@ -16,3 +16,8 @@ class AssignmentCreateView(CreateView): 'coverage', 'attestation_of_copyright', ] + success_url = reverse_lazy('assignment-thanks') + + +class AssignmentThanksView(TemplateView): + template_name = 'assignment/thanks.html' diff --git a/www/conservancy/templates/assignment/thanks.html b/www/conservancy/templates/assignment/thanks.html new file mode 100644 index 00000000..18390f7b --- /dev/null +++ b/www/conservancy/templates/assignment/thanks.html @@ -0,0 +1,27 @@ +{% extends "base_conservancy.html" %} +{% block category %}Copyright Assignment{% endblock %} +{% block outercontent %} + +

Thanks!

+ +
+

You'll shortly receive an email with the paperwork required to complete this assignment. If you have any questions or concerns, please don't hesitate to contact us.

+
+{% endblock %} diff --git a/www/conservancy/urls.py b/www/conservancy/urls.py index 5c07d2c1..f22c3357 100644 --- a/www/conservancy/urls.py +++ b/www/conservancy/urls.py @@ -59,5 +59,5 @@ urlpatterns = [ url(r'^coming-soon.html', static_views.index), url(r'^fundraiser_data', fundgoal_views.view), url(r'^ccs-upload/', include('conservancy.apps.ccs_upload.urls', namespace='ccs_upload')), - url(r'^assignment', include('conservancy.apps.assignment.urls')), + url(r'^assignment/', include('conservancy.apps.assignment.urls')), ] From d71f38a4578592b951973e21a86a7f9f49eed7f1 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 30 Nov 2021 16:38:10 +1100 Subject: [PATCH 04/16] Add migration. --- .../assignment/migrations/0001_initial.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 www/conservancy/apps/assignment/migrations/0001_initial.py diff --git a/www/conservancy/apps/assignment/migrations/0001_initial.py b/www/conservancy/apps/assignment/migrations/0001_initial.py new file mode 100644 index 00000000..806a62c3 --- /dev/null +++ b/www/conservancy/apps/assignment/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2021-11-30 00:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Assignment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('full_name', models.CharField(max_length=255)), + ('email', models.EmailField(max_length=254)), + ('place_of_residence', models.TextField(blank=True, verbose_name='Country of citizenship or residential address')), + ('repository', models.URLField(blank=True, verbose_name='Code repository')), + ('coverage', models.CharField(choices=[('up to this year', 'One-off up to and including this year'), ('ongoing', 'All existing and new contributions')], default='up to this year', max_length=50, verbose_name='Time period to assign')), + ('attestation_of_copyright', models.BooleanField(verbose_name='I attest that I own the copyright on these works')), + ], + ), + ] From 9bf3e2d03ae8f42c96ee2aeedde5f7d65da8f77f Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 7 Dec 2021 09:27:17 +1100 Subject: [PATCH 05/16] Add assignment date range. --- www/conservancy/apps/assignment/models.py | 5 +++++ www/conservancy/apps/assignment/views.py | 14 +++++++++++--- .../templates/assignment/assignment_form.html | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/www/conservancy/apps/assignment/models.py b/www/conservancy/apps/assignment/models.py index 0b8243a6..c06d5717 100644 --- a/www/conservancy/apps/assignment/models.py +++ b/www/conservancy/apps/assignment/models.py @@ -4,6 +4,8 @@ from django.db import models class Assignment(models.Model): + """A copyright assignment to Conservancy.""" + full_name = models.CharField(max_length=255) email = models.EmailField() place_of_residence = models.TextField( @@ -20,9 +22,12 @@ class Assignment(models.Model): choices=[ ('up to this year', 'One-off up to and including this year'), ('ongoing', 'All existing and new contributions'), + ('specific', 'A specific period (details below)'), ], default='up to this year', ) + coverage_from = models.DateField(blank=True) + coverage_to = models.DateField(blank=True) attestation_of_copyright = models.BooleanField( 'I attest that I own the copyright on these works' ) diff --git a/www/conservancy/apps/assignment/views.py b/www/conservancy/apps/assignment/views.py index b2475d06..4ab306df 100644 --- a/www/conservancy/apps/assignment/views.py +++ b/www/conservancy/apps/assignment/views.py @@ -1,13 +1,21 @@ -from django.shortcuts import render - +from django import forms from django.urls import reverse_lazy from django.views.generic import TemplateView from django.views.generic.edit import CreateView + from .models import Assignment -class AssignmentCreateView(CreateView): +class AssignmentForm(forms.ModelForm): model = Assignment + coverage_from = forms.DateField(required=False) + coverage_to = forms.DateField(required=False) + + +class AssignmentCreateView(CreateView): + """Show a form for the initial copyright assignment.""" + + form_class = AssignmentForm fields = [ 'full_name', 'email', diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html index ed2a07a9..376544ae 100644 --- a/www/conservancy/templates/assignment/assignment_form.html +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -33,6 +33,6 @@

Please be aware that some employment agreements explicitly transfer copyright ownership to the employer. We recommend you review your recent employment agreements for such clauses.

-

+

{% endblock %} From 58b38e93c243d307bcd602fdb31d95c32e9a0ecb Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 7 Dec 2021 16:55:45 +1100 Subject: [PATCH 06/16] Update the assignment form with feedback from Pam/Denver. --- requirements.txt | 10 ++ www/conservancy/apps/assignment/forms.py | 59 +++++++++ .../migrations/0002_auto_20211206_2237.py | 74 +++++++++++ .../migrations/0003_auto_20211206_2249.py | 25 ++++ www/conservancy/apps/assignment/models.py | 60 ++++++--- www/conservancy/apps/assignment/terms.py | 121 ++++++++++++++++++ www/conservancy/apps/assignment/urls.py | 2 +- www/conservancy/apps/assignment/views.py | 46 ++++--- www/conservancy/settings.py | 22 ++++ .../templates/assignment/assignment_form.html | 44 ++----- .../templates/assignment/thanks.html | 31 ++--- 11 files changed, 404 insertions(+), 90 deletions(-) create mode 100644 requirements.txt create mode 100644 www/conservancy/apps/assignment/forms.py create mode 100644 www/conservancy/apps/assignment/migrations/0002_auto_20211206_2237.py create mode 100644 www/conservancy/apps/assignment/migrations/0003_auto_20211206_2249.py create mode 100644 www/conservancy/apps/assignment/terms.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c4d5de34 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +backports.functools-lru-cache==1.6.4 +beautifulsoup4==4.9.3 +bs4==0.0.1 +Django==1.11.29 +pytz==2021.3 +soupsieve==1.9.6 +html5lib==0.999999999 +future + +django_countries==5.5 # Supports both Python 2 and 3. diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py new file mode 100644 index 00000000..5016ff91 --- /dev/null +++ b/www/conservancy/apps/assignment/forms.py @@ -0,0 +1,59 @@ +from django import forms +from django.core.validators import ValidationError +from django.utils import timezone + +from .models import Assignment +from .terms import TERMS + + +def validate_in_past(value): + if value >= timezone.now().date(): + raise ValidationError('Enter a date in the past') + + +class AssignmentForm(forms.ModelForm): + period_begins = forms.DateField( + label='Start of period to assign', + required=True, + widget=forms.DateInput(attrs={'type': 'date'}), + validators=[validate_in_past], + ) + period_end_type = forms.ChoiceField( + label='End of period to assign', + choices=[ + ('all future contributions', 'all future contributions'), + ('a specific past date', 'a specific past date (specify below)'), + ], + widget=forms.RadioSelect(), + ) + period_ends = forms.DateField( + label='Specific past date (if applicable)', + required=False, + widget=forms.DateInput(attrs={'type': 'date'}), + validators=[validate_in_past], + ) + agreement_terms = forms.CharField( + widget=forms.Textarea(attrs={'readonly': 'readonly'}), + initial=TERMS, + help_text='Please be aware that some employment agreements explicitly transfer copyright ownership to the employer. We recommend you review your recent employment agreements for such clauses.', + ) + + class Meta: + model = Assignment + fields = [ + 'full_name', + 'email', + 'country_of_residence', + 'repositories', + 'all_emails', + 'period_begins', + 'period_end_type', + 'period_ends', + 'agreement_terms', + 'attestation_of_copyright', + ] + + def clean_period_ends(self): + cleaned_data = super().clean() + if 'period_begins' in cleaned_data and 'period_ends' in cleaned_data and cleaned_data['period_begins'] > cleaned_data['period_ends']: + raise ValidationError('End of period is before start') diff --git a/www/conservancy/apps/assignment/migrations/0002_auto_20211206_2237.py b/www/conservancy/apps/assignment/migrations/0002_auto_20211206_2237.py new file mode 100644 index 00000000..216de0ce --- /dev/null +++ b/www/conservancy/apps/assignment/migrations/0002_auto_20211206_2237.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2021-12-06 22:37 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +import django_countries.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('assignment', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='assignment', + name='coverage', + ), + migrations.RemoveField( + model_name='assignment', + name='place_of_residence', + ), + migrations.RemoveField( + model_name='assignment', + name='repository', + ), + migrations.AddField( + model_name='assignment', + name='all_emails', + field=models.TextField(default='', verbose_name='All email addresses and/or names used by you to contribute to the above'), + preserve_default=False, + ), + migrations.AddField( + model_name='assignment', + name='country_of_residence', + field=django_countries.fields.CountryField(default='', max_length=2), + preserve_default=False, + ), + migrations.AddField( + model_name='assignment', + name='period_begins', + field=models.DateField(default=datetime.date(2021, 1, 1), verbose_name='Assignment period begins'), + preserve_default=False, + ), + migrations.AddField( + model_name='assignment', + name='period_end_type', + field=models.CharField(choices=[('all future contributions', 'all future contributions'), ('a specific past date', 'a specific past date')], default=datetime.date(2021, 1, 1), max_length=50, verbose_name='Time period to assign'), + preserve_default=False, + ), + migrations.AddField( + model_name='assignment', + name='period_ends', + field=models.DateField(blank=True, null=True, verbose_name='Assignment period ends (if applicable)'), + ), + migrations.AddField( + model_name='assignment', + name='repositories', + field=models.TextField(default='', help_text='List of URLs, one per line', verbose_name="Code repositories contributed to that you'd like to assign"), + preserve_default=False, + ), + migrations.AlterField( + model_name='assignment', + name='attestation_of_copyright', + field=models.BooleanField(verbose_name='I agree to be bound by the terms of the Copyright Assignment Agreement above, and that I own the copyright in the works defined above'), + ), + migrations.AlterField( + model_name='assignment', + name='email', + field=models.EmailField(max_length=254, verbose_name='Email address (to contact you if we have questions)'), + ), + ] diff --git a/www/conservancy/apps/assignment/migrations/0003_auto_20211206_2249.py b/www/conservancy/apps/assignment/migrations/0003_auto_20211206_2249.py new file mode 100644 index 00000000..38d265aa --- /dev/null +++ b/www/conservancy/apps/assignment/migrations/0003_auto_20211206_2249.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2021-12-06 22:49 +from __future__ import unicode_literals + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assignment', '0002_auto_20211206_2237'), + ] + + operations = [ + migrations.RemoveField( + model_name='assignment', + name='id', + ), + migrations.AddField( + model_name='assignment', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + ] diff --git a/www/conservancy/apps/assignment/models.py b/www/conservancy/apps/assignment/models.py index c06d5717..d7bb22ec 100644 --- a/www/conservancy/apps/assignment/models.py +++ b/www/conservancy/apps/assignment/models.py @@ -1,33 +1,59 @@ from __future__ import unicode_literals +import uuid + +from django.core.validators import URLValidator, ValidationError from django.db import models +from django_countries.fields import CountryField + + +def validate_mutiple_urls(value): + """Map the URLValidator() over text containing multiple URLs.""" + candidate_urls = [c.strip() for c in value.split()] + invalid_urls = [] + # TODO: Improve this https://docs.djangoproject.com/en/3.2/ref/forms/validation/#raising-multiple-errors + validator = URLValidator() + for url in candidate_urls: + try: + validator(url) + except ValidationError: + invalid_urls.append(url) + print(invalid_urls) + if invalid_urls: + raise ValidationError('These don\'t seem to be complete URLs:\n{}'.format('\n'.join(invalid_urls))) class Assignment(models.Model): """A copyright assignment to Conservancy.""" + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) full_name = models.CharField(max_length=255) - email = models.EmailField() - place_of_residence = models.TextField( - 'Country of citizenship or residential address', - blank=True) - - repository = models.URLField( - 'Code repository', - blank=True, + email = models.EmailField('Email address (to contact you if we have questions)') + country_of_residence = CountryField() + repositories = models.TextField( + 'Code repositories contributed to that you\'d like to assign', + help_text='List of URLs, one per line', + validators=[validate_mutiple_urls], ) - coverage = models.CharField( - verbose_name='Time period to assign', + all_emails = models.TextField( + 'All email addresses and/or names used by you to contribute to the above', + ) + period_begins = models.DateField( + 'Assignment period begins', + ) + period_end_type = models.CharField( + 'Time period to assign', max_length=50, choices=[ - ('up to this year', 'One-off up to and including this year'), - ('ongoing', 'All existing and new contributions'), - ('specific', 'A specific period (details below)'), + ('all future contributions', 'all future contributions'), + ('a specific past date', 'a specific past date'), ], - default='up to this year', ) - coverage_from = models.DateField(blank=True) - coverage_to = models.DateField(blank=True) + period_ends = models.DateField( + 'Assignment period ends (if applicable)', + blank=True, + null=True, + ) attestation_of_copyright = models.BooleanField( - 'I attest that I own the copyright on these works' + 'I agree to be bound by the terms of the Copyright Assignment Agreement above, and that I own the copyright in the works defined above', ) diff --git a/www/conservancy/apps/assignment/terms.py b/www/conservancy/apps/assignment/terms.py new file mode 100644 index 00000000..0d0e56d9 --- /dev/null +++ b/www/conservancy/apps/assignment/terms.py @@ -0,0 +1,121 @@ +import textwrap + +TERMS = textwrap.dedent("""\ + Copyright Assignment Agreement + + By checking the box below and submitting this form, you (``Assignor'') + enter into this Agreement between Assignor and + the Software Freedom Conservancy, Inc., a New York nonprofit corporation + located in Brooklyn, New York, which has received recognition of exemption + from federal income tax under Section 501(c)(3) of the Internal Revenue + Code and classification as a public charity (the ``Conservancy''). + + For good and valuable consideration, receipt of which is hereby + acknowledged, Assignor hereby transfers to the Conservancy its entire + right, title, and interest (including all rights under copyright) in the + work identified by the repositories, email addresses, names, and time + periods listed above, including original code, + accompanying documentation and supporting files, changes and enhancements + to the code and accompanying files, subject to the conditions + below. The original code, files, changes and enhancements, and + modifications are herein called the ``Work''. + + For the purposes of this contract, a work ``based on the Work'' means any + work that in whole or in part incorporates or is derived from all or part + of the Work. The Conservancy promises that the Work and any work ``based + on the Work'' distributed by the Conservancy or its assignees will be + distributed under one or more of the following licenses: + + * the license as set forth in Exhibit A (the ``MIT License''), + + * the GNU General Public License v2 or any later version (``GPL''), + as published by the Free Software Foundation, Inc., + + * the ``CC-By'' license as published by the Creative Commons, Inc., + + * the Creative Commons Attribution-ShareAlike 3.0 United States license + (``CC-By-SA''), + + * any other license determined to be a free software license by the + Free Software Foundation (``FSF'') and approved as an open source + license by the Open Source Initiative (``OSI''), + + * any other license determined to be a free culture compatible + license by the Creative Commons Corporation (``Creative + Commons'') and freedomdefined.org (``Freedom Defined''). + + In the event that either FSF or OSI ceases to maintain a list of approved + licenses for a period of one year and, for a period of six months, fails + to respond to a written request from the Conservancy regarding evaluation + of a new license which is not currently listed on either approved lists + (is ``Dormant''), the work may be distributed under that new license, + provided that new license is approved as a free software or open source + license by one of FSF or OSI, and the Conservancy also independently + determines the new license will allow the software to be freely copied, + modified, and redistributed by all its users (is a ``Free License''). In + the event that both FSF and OSI are Dormant, the Work may be distributed + under a license the Conservancy independently determines is a Free + Software License. + + In the event that either Creative Commons or Freedom Defined is Dormant, + the Work may be distributed under that new license, provided that new + license is approved as a free culture compatible license by one of the + Creative Commons or Freedom Defined, and the Conservancy also + independently determines the new license is a Free License. In the event + that both Creative Commons and Freedom Defined are Dormant, the Work may + be distributed under a license the Conservancy independently determines is + a Free Culture License. + + The Conservancy promises that any program ``based on the Work'' offered to + the public by the Conservancy or its assignees shall be offered in a + machine-readable source format, in addition to any other forms of the + Conservancy's choosing. However, the Conservancy is free to choose at its + convenience the media of distribution for the machine-readable source + format. + + The Conservancy hereby grants Assignor a royalty-free non-exclusive + license to use or sub-license the interests assigned hereunder for any + purpose. The Conservancy's rights shall otherwise continue unchanged. + + Assignor hereby grants to the Conservancy and to recipients of software + distributed by the Conservancy a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, where + such license applies only to those patent claims licensable by Assignor + that are necessarily infringed by the Work alone or by combination of + Assignor's contributions with the Work to which such contributions were + submitted. + + Assignor hereby represents and warrants that it is the sole copyright + holder for the Work assigned hereunder and that it has the right and power + to enter into this contract. Assignor hereby indemnifies and holds + harmless the Conservancy, its officers, employees, and agents against any + and all claims, actions or damages (including reasonable attorney's fees) + asserted by or paid to any party on account of a breach or alleged breach + of the foregoing warranty. Assignor makes no other express or implied + warranty (including without limitation, in this disclaimer of warranty, + any warranty of merchantability or fitness for a particular + purpose). + + + Exhibit A + The MIT License + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + The software is provided ``as is'', without warranty of any kind, express or + implied, including but not limited to the warranties of merchantability, + fitness for a particular purpose and noninfringement. In no event shall + the authors or copyright holders be liable for any claim, damages or other + liability, whether in an action of contract, tort or otherwise, arising + from, out of or in connection with the software or the use or other + dealings in the software.""") diff --git a/www/conservancy/apps/assignment/urls.py b/www/conservancy/apps/assignment/urls.py index 1be0b775..0502b0ef 100644 --- a/www/conservancy/apps/assignment/urls.py +++ b/www/conservancy/apps/assignment/urls.py @@ -5,5 +5,5 @@ from .views import AssignmentCreateView, AssignmentThanksView urlpatterns = [ url(r'^$', AssignmentCreateView.as_view(), name='assignement-add'), - url(r'^thanks/$', AssignmentThanksView.as_view(), name='assignment-thanks'), + url(r'^(?P[\w-]+)/$', AssignmentThanksView.as_view(), name='assignment-thanks'), ] diff --git a/www/conservancy/apps/assignment/views.py b/www/conservancy/apps/assignment/views.py index 4ab306df..ac455460 100644 --- a/www/conservancy/apps/assignment/views.py +++ b/www/conservancy/apps/assignment/views.py @@ -1,31 +1,39 @@ -from django import forms +from django.core.mail import send_mail from django.urls import reverse_lazy -from django.views.generic import TemplateView +from django.views.generic import DetailView from django.views.generic.edit import CreateView +from .forms import AssignmentForm from .models import Assignment - -class AssignmentForm(forms.ModelForm): - model = Assignment - coverage_from = forms.DateField(required=False) - coverage_to = forms.DateField(required=False) - - class AssignmentCreateView(CreateView): """Show a form for the initial copyright assignment.""" form_class = AssignmentForm - fields = [ - 'full_name', - 'email', - 'place_of_residence', - 'repository', - 'coverage', - 'attestation_of_copyright', - ] - success_url = reverse_lazy('assignment-thanks') + template_name = 'assignment/assignment_form.html' + + def form_valid(self, form): + intro = 'The following copyright assignment has been submitted:\n\n' + body = intro + '\n'.join(['{}: {}'.format(k, v) for k, v in form.cleaned_data.items() if k != 'agreement_terms']) + send_mail( + 'Copyright assignment form: {}'.format(form.cleaned_data['full_name']), + body, + 'ben@sturm.com.au', + ['denver@sfconservancy.org', 'bsturmfels@sfconservancy.org'], + ) + return super().form_valid(form) + + def get_success_url(self, *args, **kwargs): + return reverse_lazy('assignment-thanks', kwargs={'pk': str(self.object.uuid)}) -class AssignmentThanksView(TemplateView): +class AssignmentThanksView(DetailView): + model = Assignment template_name = 'assignment/thanks.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['form'] = AssignmentForm(instance=self.object) + for _, field in context['form'].fields.items(): + field.widget.attrs['disabled'] = 'disabled' + return context diff --git a/www/conservancy/settings.py b/www/conservancy/settings.py index 85ebceb4..4d23f849 100644 --- a/www/conservancy/settings.py +++ b/www/conservancy/settings.py @@ -84,3 +84,25 @@ LOGGING = { 'level': 'INFO', }, } + +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.messages', + 'django.contrib.humanize', + # 'django.contrib.staticfiles', + 'conservancy.apps.blog', + 'conservancy.apps.contacts', + 'conservancy.apps.contractpatch', + 'conservancy.apps.events', + 'conservancy.apps.news', + 'conservancy.apps.staff', + # 'conservancy.apps.summit_registration', + 'conservancy.apps.worldmap', + 'conservancy.apps.supporters', + 'conservancy.apps.fundgoal', + 'conservancy.apps.assignment', +] diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html index 376544ae..753cd427 100644 --- a/www/conservancy/templates/assignment/assignment_form.html +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -1,38 +1,20 @@ -{% extends "base_conservancy.html" %} +{% extends "assignment/base_assignment.html" %} {% block category %}Copyright Assignment{% endblock %} {% block outercontent %} -

Copyright Assignment

-
-

Thank you for considering assigning your copyright to the Software Freedom Conservancy. Your assignment helps us enforce copyright on your behalf.

+
+

Thank you for considering assigning your copyright to the Software Freedom Conservancy. Your assignment helps us enforce free and open source software licenses.

-

Please complete the following form and we will prepare the appropriate paperwork. You will receive a PDF form to sign and be witnessed by a public notary.

+

By filling in and submitting the below form, you agree to assign your copyrights in the specified projects to Software Freedom Conservancy, which means that Conservancy can enforce the licenses that your code is under in court, without you needing to be involved. Conservancy agrees to keep your code under a free and open source license.

+ +

If you have any questions about assigning your copyright to Conservancy, please don't hesitate to email us at info@sfconservancy.org.

+ +
+ {% csrf_token %} + {{ form.as_p }} + +

+
- -
- {% csrf_token %} - {{ form.as_p }} - -

Please be aware that some employment agreements explicitly transfer copyright ownership to the employer. We recommend you review your recent employment agreements for such clauses.

- -

-
{% endblock %} diff --git a/www/conservancy/templates/assignment/thanks.html b/www/conservancy/templates/assignment/thanks.html index 18390f7b..86be3ba4 100644 --- a/www/conservancy/templates/assignment/thanks.html +++ b/www/conservancy/templates/assignment/thanks.html @@ -1,27 +1,14 @@ -{% extends "base_conservancy.html" %} +{% extends "assignment/base_assignment.html" %} +{% load static %} {% block category %}Copyright Assignment{% endblock %} {% block outercontent %} - -

Thanks!

+

Thanks!

-
-

You'll shortly receive an email with the paperwork required to complete this assignment. If you have any questions or concerns, please don't hesitate to contact us.

+
+

Thank you for assigning your copyright to Software Freedom Conservancy! We have recorded the below information regarding the assignment and the works.

+

If you would like to make any changes, you must let us know within 7 days by emailing info@sfconservancy.org. Thanks for helping us enforce free and open source software licenses!

+
+ {{ form.as_p }} +
{% endblock %} From a505a1c9f014477f2962addcfd41ae63b5ce3b6e Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 7 Dec 2021 17:02:04 +1100 Subject: [PATCH 07/16] Add missing template. --- .../templates/assignment/base_assignment.html | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 www/conservancy/templates/assignment/base_assignment.html diff --git a/www/conservancy/templates/assignment/base_assignment.html b/www/conservancy/templates/assignment/base_assignment.html new file mode 100644 index 00000000..6aec1c42 --- /dev/null +++ b/www/conservancy/templates/assignment/base_assignment.html @@ -0,0 +1,49 @@ +{% extends "base_conservancy.html" %} +{% block category %}Copyright Assignment{% endblock %} +{% block head %} + {{ block.super }} + +{% endblock %} From 2ef3d7638f07c8255d7633820323fccf95718ad6 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 7 Dec 2021 17:06:37 +1100 Subject: [PATCH 08/16] Remove redundant call to super(). --- www/conservancy/apps/assignment/forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index 5016ff91..83876d47 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -54,6 +54,5 @@ class AssignmentForm(forms.ModelForm): ] def clean_period_ends(self): - cleaned_data = super().clean() if 'period_begins' in cleaned_data and 'period_ends' in cleaned_data and cleaned_data['period_begins'] > cleaned_data['period_ends']: raise ValidationError('End of period is before start') From fbef5db8cef4ee172405fdbe94cf0504ff599b7e Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Tue, 7 Dec 2021 17:49:57 +1100 Subject: [PATCH 09/16] Fix validation. --- www/conservancy/apps/assignment/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index 83876d47..f6933355 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -54,5 +54,5 @@ class AssignmentForm(forms.ModelForm): ] def clean_period_ends(self): - if 'period_begins' in cleaned_data and 'period_ends' in cleaned_data and cleaned_data['period_begins'] > cleaned_data['period_ends']: + if 'period_begins' in self.cleaned_data and 'period_ends' in self.cleaned_data and self.cleaned_data['period_begins'] and self.cleaned_data['period_ends'] and self.cleaned_data['period_begins'] > self.cleaned_data['period_ends']: raise ValidationError('End of period is before start') From d3f3074f3882a1727d9838de81b11bd3170cde8b Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Fri, 10 Dec 2021 11:33:37 +1100 Subject: [PATCH 10/16] assignment: Apply Denver's requested changes. --- www/conservancy/apps/assignment/forms.py | 12 ++++++--- www/conservancy/apps/assignment/models.py | 4 +-- www/conservancy/apps/assignment/terms.py | 18 ++++++------- .../templates/assignment/assignment_form.html | 27 +++++++++++++++++-- .../templates/assignment/base_assignment.html | 2 +- .../templates/assignment/thanks.html | 3 ++- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index f6933355..8777e0b4 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -13,18 +13,20 @@ def validate_in_past(value): class AssignmentForm(forms.ModelForm): period_begins = forms.DateField( - label='Start of period to assign', + label='Assign the copyright in my above contributions starting on', + help_text='You can use the day you first started contributing (or, equivalently, your date of birth), or any later date.', required=True, widget=forms.DateInput(attrs={'type': 'date'}), validators=[validate_in_past], ) period_end_type = forms.ChoiceField( - label='End of period to assign', + label='and ending on', choices=[ - ('all future contributions', 'all future contributions'), + ('all future contributions', 'all future contributions (no end date)'), ('a specific past date', 'a specific past date (specify below)'), ], widget=forms.RadioSelect(), + initial='all future contributions', ) period_ends = forms.DateField( label='Specific past date (if applicable)', @@ -38,6 +40,10 @@ class AssignmentForm(forms.ModelForm): help_text='Please be aware that some employment agreements explicitly transfer copyright ownership to the employer. We recommend you review your recent employment agreements for such clauses.', ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['attestation_of_copyright'].required = True + class Meta: model = Assignment fields = [ diff --git a/www/conservancy/apps/assignment/models.py b/www/conservancy/apps/assignment/models.py index d7bb22ec..f69965b8 100644 --- a/www/conservancy/apps/assignment/models.py +++ b/www/conservancy/apps/assignment/models.py @@ -31,12 +31,12 @@ class Assignment(models.Model): email = models.EmailField('Email address (to contact you if we have questions)') country_of_residence = CountryField() repositories = models.TextField( - 'Code repositories contributed to that you\'d like to assign', + 'Code repositories containing contributions of yours whose copyright you\'d like to assign', help_text='List of URLs, one per line', validators=[validate_mutiple_urls], ) all_emails = models.TextField( - 'All email addresses and/or names used by you to contribute to the above', + 'All email addresses used by you to contribute to the above (i.e. in the commit logs)', ) period_begins = models.DateField( 'Assignment period begins', diff --git a/www/conservancy/apps/assignment/terms.py b/www/conservancy/apps/assignment/terms.py index 0d0e56d9..75549e2e 100644 --- a/www/conservancy/apps/assignment/terms.py +++ b/www/conservancy/apps/assignment/terms.py @@ -94,8 +94,8 @@ TERMS = textwrap.dedent("""\ asserted by or paid to any party on account of a breach or alleged breach of the foregoing warranty. Assignor makes no other express or implied warranty (including without limitation, in this disclaimer of warranty, - any warranty of merchantability or fitness for a particular - purpose). + any warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE). Exhibit A @@ -112,10 +112,10 @@ TERMS = textwrap.dedent("""\ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - The software is provided ``as is'', without warranty of any kind, express or - implied, including but not limited to the warranties of merchantability, - fitness for a particular purpose and noninfringement. In no event shall - the authors or copyright holders be liable for any claim, damages or other - liability, whether in an action of contract, tort or otherwise, arising - from, out of or in connection with the software or the use or other - dealings in the software.""") + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE.""") diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html index 753cd427..ea13b9a1 100644 --- a/www/conservancy/templates/assignment/assignment_form.html +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -10,11 +10,34 @@

If you have any questions about assigning your copyright to Conservancy, please don't hesitate to email us at info@sfconservancy.org.

-
+ {% csrf_token %} {{ form.as_p }} -

+

+ + {% endblock %} diff --git a/www/conservancy/templates/assignment/base_assignment.html b/www/conservancy/templates/assignment/base_assignment.html index 6aec1c42..1af63a6c 100644 --- a/www/conservancy/templates/assignment/base_assignment.html +++ b/www/conservancy/templates/assignment/base_assignment.html @@ -26,7 +26,7 @@ } textarea { width: 100%; - max-width: 35rem; + max-width: 45rem; height: 8rem; padding: 0.25rem; } diff --git a/www/conservancy/templates/assignment/thanks.html b/www/conservancy/templates/assignment/thanks.html index 86be3ba4..e8be9ec9 100644 --- a/www/conservancy/templates/assignment/thanks.html +++ b/www/conservancy/templates/assignment/thanks.html @@ -6,7 +6,8 @@

Thank you for assigning your copyright to Software Freedom Conservancy! We have recorded the below information regarding the assignment and the works.

-

If you would like to make any changes, you must let us know within 7 days by emailing info@sfconservancy.org. Thanks for helping us enforce free and open source software licenses!

+

We will be sending out verification emails to the email addresses you used to contribute, as specified below, in the coming weeks. Please follow the instructions there to complete the verification at that time.

+

If you would like to make any changes, you must let us know within 7 days by emailing info@sfconservancy.org, which is also where you can reach us if you have any questions.

{{ form.as_p }}
From 56bcbd1e0048e108aef52cae77dbb977c93d0d83 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Fri, 10 Dec 2021 18:37:13 +1100 Subject: [PATCH 11/16] assignment: Validate that end date is provided if you didn't choose open-ended. --- www/conservancy/apps/assignment/forms.py | 3 +++ .../templates/assignment/assignment_form.html | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index 8777e0b4..288dc75a 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -62,3 +62,6 @@ class AssignmentForm(forms.ModelForm): def clean_period_ends(self): if 'period_begins' in self.cleaned_data and 'period_ends' in self.cleaned_data and self.cleaned_data['period_begins'] and self.cleaned_data['period_ends'] and self.cleaned_data['period_begins'] > self.cleaned_data['period_ends']: raise ValidationError('End of period is before start') + + if self.cleaned_data['period_end_type'] == 'a specific past date' and not self.cleaned_data['period_ends']: + raise ValidationError('This field is required') diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html index ea13b9a1..d7c2cc1a 100644 --- a/www/conservancy/templates/assignment/assignment_form.html +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -24,7 +24,8 @@ // End date field should be shown only when "a specific past date" is selected. const form = document.querySelector('#assignment-form'); const past_date_label = document.querySelector('label[for=id_period_ends]'); - const past_date_field = document.querySelector('#assignment-form > p:nth-child(11)'); + const past_date_field = document.querySelector('#id_period_ends'); + const past_date_container = past_date_field.parentElement; form.addEventListener('change', togglePastDate); togglePastDate(); // Run change handler once to initialise form. @@ -33,10 +34,12 @@ function togglePastDate() { if (form['period_end_type'].value === 'all future contributions') { - past_date_field.style.display = 'none'; + past_date_container.style.display = 'none'; + past_date_field.required = false; } else { - past_date_field.style.display = ''; + past_date_container.style.display = ''; + past_date_field.required = true; } } From 4355253e6c06cfe7b325eed8b2feede2df686fb8 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Sat, 11 Dec 2021 10:25:57 +1100 Subject: [PATCH 12/16] assignment: Fix period ends in email. --- www/conservancy/apps/assignment/forms.py | 2 ++ www/conservancy/apps/assignment/views.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index 288dc75a..cd93ea3f 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -65,3 +65,5 @@ class AssignmentForm(forms.ModelForm): if self.cleaned_data['period_end_type'] == 'a specific past date' and not self.cleaned_data['period_ends']: raise ValidationError('This field is required') + + return self.cleaned_data['period_ends'] diff --git a/www/conservancy/apps/assignment/views.py b/www/conservancy/apps/assignment/views.py index ac455460..c782a61e 100644 --- a/www/conservancy/apps/assignment/views.py +++ b/www/conservancy/apps/assignment/views.py @@ -22,7 +22,7 @@ class AssignmentCreateView(CreateView): ['denver@sfconservancy.org', 'bsturmfels@sfconservancy.org'], ) return super().form_valid(form) - + def get_success_url(self, *args, **kwargs): return reverse_lazy('assignment-thanks', kwargs={'pk': str(self.object.uuid)}) From 8065de9b5cb7bfd2f18c2abfd888aa9ee4782f82 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Sat, 11 Dec 2021 10:40:07 +1100 Subject: [PATCH 13/16] assignment: Mention 7 days on form. --- www/conservancy/templates/assignment/assignment_form.html | 1 + 1 file changed, 1 insertion(+) diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html index d7c2cc1a..e4c66af2 100644 --- a/www/conservancy/templates/assignment/assignment_form.html +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -14,6 +14,7 @@ {% csrf_token %} {{ form.as_p }} +

After submitting this agreement, if you would like to make any changes, you must let us know within 7 days by emailing info@sfconservancy.org, which is also where you can reach us if you have any questions.

From 944a69c0e7c4ec0d050feef86ae8d992f4796629 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Sat, 11 Dec 2021 11:29:13 +1100 Subject: [PATCH 14/16] assignment: Allow today's date. --- www/conservancy/apps/assignment/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index cd93ea3f..e2c390ff 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -7,7 +7,7 @@ from .terms import TERMS def validate_in_past(value): - if value >= timezone.now().date(): + if value > timezone.now().date(): raise ValidationError('Enter a date in the past') From ecdfd87ffbc340b05b3d9a16dfb6737d3bafd477 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Fri, 17 Dec 2021 09:50:41 +1100 Subject: [PATCH 15/16] assignment: Apply further wording updates from Pam/Denver. --- www/conservancy/apps/assignment/forms.py | 1 - www/conservancy/apps/assignment/models.py | 6 +- www/conservancy/apps/assignment/terms.py | 144 +++++------------- .../templates/assignment/assignment_form.html | 8 +- .../templates/assignment/thanks.html | 2 +- 5 files changed, 48 insertions(+), 113 deletions(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index e2c390ff..a55f07a9 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -37,7 +37,6 @@ class AssignmentForm(forms.ModelForm): agreement_terms = forms.CharField( widget=forms.Textarea(attrs={'readonly': 'readonly'}), initial=TERMS, - help_text='Please be aware that some employment agreements explicitly transfer copyright ownership to the employer. We recommend you review your recent employment agreements for such clauses.', ) def __init__(self, *args, **kwargs): diff --git a/www/conservancy/apps/assignment/models.py b/www/conservancy/apps/assignment/models.py index f69965b8..37317939 100644 --- a/www/conservancy/apps/assignment/models.py +++ b/www/conservancy/apps/assignment/models.py @@ -31,12 +31,12 @@ class Assignment(models.Model): email = models.EmailField('Email address (to contact you if we have questions)') country_of_residence = CountryField() repositories = models.TextField( - 'Code repositories containing contributions of yours whose copyright you\'d like to assign', + 'Code repositories containing contributions of yours whose copyright you are assigning', help_text='List of URLs, one per line', validators=[validate_mutiple_urls], ) all_emails = models.TextField( - 'All email addresses used by you to contribute to the above (i.e. in the commit logs)', + 'All email addresses or other unique user identities, such as nicknames or handles, used by you to contribute to the above (i.e. in the commit logs)', ) period_begins = models.DateField( 'Assignment period begins', @@ -55,5 +55,5 @@ class Assignment(models.Model): null=True, ) attestation_of_copyright = models.BooleanField( - 'I agree to be bound by the terms of the Copyright Assignment Agreement above, and that I own the copyright in the works defined above', + 'By checking the box below, I am confirming that I agree to be bound by the terms of the Copyright Assignment Agreement above.', ) diff --git a/www/conservancy/apps/assignment/terms.py b/www/conservancy/apps/assignment/terms.py index 75549e2e..72988313 100644 --- a/www/conservancy/apps/assignment/terms.py +++ b/www/conservancy/apps/assignment/terms.py @@ -3,119 +3,53 @@ import textwrap TERMS = textwrap.dedent("""\ Copyright Assignment Agreement - By checking the box below and submitting this form, you (``Assignor'') - enter into this Agreement between Assignor and - the Software Freedom Conservancy, Inc., a New York nonprofit corporation - located in Brooklyn, New York, which has received recognition of exemption - from federal income tax under Section 501(c)(3) of the Internal Revenue - Code and classification as a public charity (the ``Conservancy''). + By checking the box below and submitting this form, you (``Assignor'') enter + into this Agreement between Assignor and the Software Freedom Conservancy, + Inc., a New York nonprofit corporation located in Brooklyn, New York, which + has received recognition of exemption from federal income tax under Section + 501(c)(3) of the Internal Revenue Code and classification as a public + charity (the ``Conservancy''). For good and valuable consideration, receipt of which is hereby - acknowledged, Assignor hereby transfers to the Conservancy its entire - right, title, and interest (including all rights under copyright) in the - work identified by the repositories, email addresses, names, and time - periods listed above, including original code, - accompanying documentation and supporting files, changes and enhancements - to the code and accompanying files, subject to the conditions - below. The original code, files, changes and enhancements, and - modifications are herein called the ``Work''. + acknowledged, Assignor hereby transfers to the Conservancy their entire + right, title, and interest in the copyright in the works identified by the + repositories, email addresses, names, and time periods listed above (the + “Works”). To the extent that the Assignor is assigning future rights, and + the rights in future works do not vest in Conservancy upon the Works’ + creation, Assignor agrees to assign, immediately following the creation, all + rights to Conservancy and irrevocably appoint Conservancy as their + attorney-in-fact to take any necessary steps to perfect Conservancy’s rights + under this Agreement. - For the purposes of this contract, a work ``based on the Work'' means any - work that in whole or in part incorporates or is derived from all or part - of the Work. The Conservancy promises that the Work and any work ``based - on the Work'' distributed by the Conservancy or its assignees will be - distributed under one or more of the following licenses: + The Conservancy will use its discretion for any relicensing of the Works + under other free and open source software licenses. Decisions about + relicensing made by Conservancy will apply to its assignees and successors. + If you have any questions about Conservancy's relicensing philosophy, and + what limitations it may have because it is a public charity, please contact + us at copyright-assignment@sfconservancy.org . - * the license as set forth in Exhibit A (the ``MIT License''), - - * the GNU General Public License v2 or any later version (``GPL''), - as published by the Free Software Foundation, Inc., - - * the ``CC-By'' license as published by the Creative Commons, Inc., - - * the Creative Commons Attribution-ShareAlike 3.0 United States license - (``CC-By-SA''), - - * any other license determined to be a free software license by the - Free Software Foundation (``FSF'') and approved as an open source - license by the Open Source Initiative (``OSI''), - - * any other license determined to be a free culture compatible - license by the Creative Commons Corporation (``Creative - Commons'') and freedomdefined.org (``Freedom Defined''). - - In the event that either FSF or OSI ceases to maintain a list of approved - licenses for a period of one year and, for a period of six months, fails - to respond to a written request from the Conservancy regarding evaluation - of a new license which is not currently listed on either approved lists - (is ``Dormant''), the work may be distributed under that new license, - provided that new license is approved as a free software or open source - license by one of FSF or OSI, and the Conservancy also independently - determines the new license will allow the software to be freely copied, - modified, and redistributed by all its users (is a ``Free License''). In - the event that both FSF and OSI are Dormant, the Work may be distributed - under a license the Conservancy independently determines is a Free - Software License. - - In the event that either Creative Commons or Freedom Defined is Dormant, - the Work may be distributed under that new license, provided that new - license is approved as a free culture compatible license by one of the - Creative Commons or Freedom Defined, and the Conservancy also - independently determines the new license is a Free License. In the event - that both Creative Commons and Freedom Defined are Dormant, the Work may - be distributed under a license the Conservancy independently determines is - a Free Culture License. - - The Conservancy promises that any program ``based on the Work'' offered to - the public by the Conservancy or its assignees shall be offered in a - machine-readable source format, in addition to any other forms of the - Conservancy's choosing. However, the Conservancy is free to choose at its - convenience the media of distribution for the machine-readable source - format. - - The Conservancy hereby grants Assignor a royalty-free non-exclusive - license to use or sub-license the interests assigned hereunder for any - purpose. The Conservancy's rights shall otherwise continue unchanged. + The Conservancy hereby grants Assignor a royalty-free non-exclusive license + to use the interests assigned hereunder for any purpose. The Conservancy's + rights shall otherwise continue unchanged. Assignor hereby grants to the Conservancy and to recipients of software distributed by the Conservancy a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, where + use, offer to sell, sell, import, and otherwise transfer the Works, where such license applies only to those patent claims licensable by Assignor - that are necessarily infringed by the Work alone or by combination of - Assignor's contributions with the Work to which such contributions were + that are necessarily infringed by the Works alone or by combination of + Assignor's contributions with the Works to which such contributions were submitted. - Assignor hereby represents and warrants that it is the sole copyright - holder for the Work assigned hereunder and that it has the right and power - to enter into this contract. Assignor hereby indemnifies and holds - harmless the Conservancy, its officers, employees, and agents against any - and all claims, actions or damages (including reasonable attorney's fees) - asserted by or paid to any party on account of a breach or alleged breach - of the foregoing warranty. Assignor makes no other express or implied - warranty (including without limitation, in this disclaimer of warranty, - any warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - PURPOSE). - - - Exhibit A - The MIT License - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the - ``Software''), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to permit - persons to whom the Software is furnished to do so, subject to the - following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE.""") + Assignor hereby represents and warrants that the email addresses and user + identities identified above are unique to the Assignor, that no one but the + Assignor has made contributions using the email addresses and user + identities identified above, that the Assignor is the sole copyright holder + for the Works assigned hereunder, and that the Assignor has the right and + power to enter into this contract. Assignor hereby indemnifies and holds + harmless the Conservancy, its officers, employees, agents, successors, and + assigns against any and all claims, actions or damages (including reasonable + attorney's fees) asserted by or paid to any party on account of a breach or + alleged breach of the foregoing warranty. Assignor makes no other express or + implied warranty (including without limitation any warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE).""") diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html index e4c66af2..c54355b1 100644 --- a/www/conservancy/templates/assignment/assignment_form.html +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -6,15 +6,17 @@

Thank you for considering assigning your copyright to the Software Freedom Conservancy. Your assignment helps us enforce free and open source software licenses.

-

By filling in and submitting the below form, you agree to assign your copyrights in the specified projects to Software Freedom Conservancy, which means that Conservancy can enforce the licenses that your code is under in court, without you needing to be involved. Conservancy agrees to keep your code under a free and open source license.

+

By filling in and submitting the below form, you agree to assign your copyrights in the specified projects to Software Freedom Conservancy, which means that Conservancy can enforce the licenses for your code in court, minimizing the need for you to be involved. Conservancy agrees to keep your code under a free and open source license.

-

If you have any questions about assigning your copyright to Conservancy, please don't hesitate to email us at info@sfconservancy.org.

+

If you have any questions about assigning your copyright to Conservancy, please don't hesitate to email us at copyright-assignment@sfconservancy.org.

{% csrf_token %} {{ form.as_p }} -

After submitting this agreement, if you would like to make any changes, you must let us know within 7 days by emailing info@sfconservancy.org, which is also where you can reach us if you have any questions.

+

Please be aware that your employer or a contractor may own the rights in your work by virtue of their employment of you or by explicit transfer of ownership in an agreement. We recommend you review any relevant agreements or consult with a lawyer if you are not sure.

+ +

After submitting this agreement, if you would like to make any changes, you must let us know within 7 days by emailing copyright-assignment@sfconservancy.org, which is also where you can reach us if you have any questions.

diff --git a/www/conservancy/templates/assignment/thanks.html b/www/conservancy/templates/assignment/thanks.html index e8be9ec9..04a916a5 100644 --- a/www/conservancy/templates/assignment/thanks.html +++ b/www/conservancy/templates/assignment/thanks.html @@ -7,7 +7,7 @@

Thank you for assigning your copyright to Software Freedom Conservancy! We have recorded the below information regarding the assignment and the works.

We will be sending out verification emails to the email addresses you used to contribute, as specified below, in the coming weeks. Please follow the instructions there to complete the verification at that time.

-

If you would like to make any changes, you must let us know within 7 days by emailing info@sfconservancy.org, which is also where you can reach us if you have any questions.

+

If you would like to make any changes, you must let us know within 7 days by emailing copyright-assignment@sfconservancy.org, which is also where you can reach us if you have any questions.

{{ form.as_p }}
From 3021e0d326300bb3c273917afcb3a7e7d41f4e72 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Fri, 17 Dec 2021 10:54:47 +1100 Subject: [PATCH 16/16] assignment: Accept current date anywhere on earth. --- www/conservancy/apps/assignment/forms.py | 6 +++++- www/conservancy/templates/assignment/assignment_form.html | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/www/conservancy/apps/assignment/forms.py b/www/conservancy/apps/assignment/forms.py index a55f07a9..1497e978 100644 --- a/www/conservancy/apps/assignment/forms.py +++ b/www/conservancy/apps/assignment/forms.py @@ -1,3 +1,5 @@ +import datetime + from django import forms from django.core.validators import ValidationError from django.utils import timezone @@ -7,7 +9,9 @@ from .terms import TERMS def validate_in_past(value): - if value > timezone.now().date(): + # Adding a day to allow the current date anywhere on earth, regardless of + # the server timezone. + if value > timezone.now().date() + datetime.timedelta(days=1): raise ValidationError('Enter a date in the past') diff --git a/www/conservancy/templates/assignment/assignment_form.html b/www/conservancy/templates/assignment/assignment_form.html index c54355b1..5ef74338 100644 --- a/www/conservancy/templates/assignment/assignment_form.html +++ b/www/conservancy/templates/assignment/assignment_form.html @@ -12,6 +12,11 @@
{% csrf_token %} + + {% if form.errors %} +

Please review the errors below.

+ {% endif %} + {{ form.as_p }}

Please be aware that your employer or a contractor may own the rights in your work by virtue of their employment of you or by explicit transfer of ownership in an agreement. We recommend you review any relevant agreements or consult with a lawyer if you are not sure.