diff --git a/books/constants.py b/books/constants.py index a12c5761d..f4821302f 100644 --- a/books/constants.py +++ b/books/constants.py @@ -66,3 +66,10 @@ (RED, 'Red'), (YELLOW, 'Yellow'), ) + +CC_BY_LICENSE_NAME = 'Creative Commons Attribution License' +CC_BY_LICENSE_VERSION = '4.0' +CC_BY_LICENSE_URL = 'https://creativecommons.org/licenses/by/4.0/' +CC_NC_SA_LICENSE_NAME = 'Creative Commons Attribution-NonCommercial-ShareAlike License' +CC_NC_SA_LICENSE_VERSION = '4.0' +CC_NC_SA_LICENSE_URL = 'https://creativecommons.org/licenses/by-nc-sa/4.0/' \ No newline at end of file diff --git a/books/management/commands/add_missing_licenses.py b/books/management/commands/add_missing_licenses.py new file mode 100644 index 000000000..48f02bcf4 --- /dev/null +++ b/books/management/commands/add_missing_licenses.py @@ -0,0 +1,18 @@ +from django.core.management.base import BaseCommand + +from snippets.models import ContentLicense +from books.models import Book + +class Command(BaseCommand): + help="add CC license to live books that do not have one" + + def handle(self, *args, **options): + nc_sa_books = ['Cálculo volumen 1', 'Cálculo volumen 2', 'Cálculo volumen 3'] + books = Book.objects.filter(license_name=None, book_state='live') + for book in books: + if book.book_title in nc_sa_books: + book.license_name = 'Creative Commons Attribution-NonCommercial-ShareAlike License' + else: + book.license_name = 'Creative Commons Attribution License' + book.save() + print('Updated license for ' + str(book.book_title)) diff --git a/books/migrations/0134_alter_book_license_name.py b/books/migrations/0134_alter_book_license_name.py new file mode 100644 index 000000000..a6dcb1c61 --- /dev/null +++ b/books/migrations/0134_alter_book_license_name.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2022-05-09 20:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('books', '0133_bookcategories_bookcategory'), + ] + + operations = [ + migrations.AlterField( + model_name='book', + name='license_name', + field=models.CharField(blank=True, choices=[('Creative Commons Attribution License', 'Creative Commons Attribution License'), ('Creative Commons Attribution-NonCommercial-ShareAlike License', 'Creative Commons Attribution-NonCommercial-ShareAlike License')], help_text='Name of the license.', max_length=255, null=True), + ), + ] diff --git a/books/migrations/0135_alter_book_license_name.py b/books/migrations/0135_alter_book_license_name.py new file mode 100644 index 000000000..034aa7d7b --- /dev/null +++ b/books/migrations/0135_alter_book_license_name.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2022-05-11 12:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('books', '0134_alter_book_license_name'), + ] + + operations = [ + migrations.AlterField( + model_name='book', + name='license_name', + field=models.CharField(blank=True, choices=[('Creative Commons Attribution License', 'Creative Commons Attribution License'), ('Creative Commons Attribution-NonCommercial-ShareAlike License', 'Creative Commons Attribution-NonCommercial-ShareAlike License')], default='Creative Commons Attribution License', help_text='Name of the license.', max_length=255, null=True), + ), + ] diff --git a/books/models.py b/books/models.py index 6deba14a2..a242490d2 100644 --- a/books/models.py +++ b/books/models.py @@ -29,7 +29,8 @@ from wagtail.core.models import Site from openstax.functions import build_document_url, build_image_url -from books.constants import BOOK_STATES, BOOK_COVER_TEXT_COLOR, COVER_COLORS +from books.constants import BOOK_STATES, BOOK_COVER_TEXT_COLOR, COVER_COLORS, CC_NC_SA_LICENSE_NAME, CC_BY_LICENSE_NAME, \ + CC_BY_LICENSE_URL, CC_NC_SA_LICENSE_URL, CC_NC_SA_LICENSE_VERSION, CC_BY_LICENSE_VERSION import snippets.models as snippets @@ -451,6 +452,11 @@ class BookCategories(Orderable, BookCategory): class Book(Page): + licenses = ( + (CC_BY_LICENSE_NAME, CC_BY_LICENSE_NAME), + (CC_NC_SA_LICENSE_NAME, CC_NC_SA_LICENSE_NAME) + ) + created = models.DateTimeField(auto_now_add=True) book_state = models.CharField(max_length=255, choices=BOOK_STATES, default='live', help_text='The state of the book.') cnx_id = models.CharField( @@ -506,7 +512,7 @@ def get_title_image_url(self): license_text = models.TextField( blank=True, null=True, help_text="Overrides default license text.") license_name = models.CharField( - max_length=255, blank=True, null=True, editable=False, help_text="Name of the license.") + max_length=255, blank=True, null=True, choices=licenses,default=CC_BY_LICENSE_NAME, help_text="Name of the license.") license_version = models.CharField( max_length=255, blank=True, null=True, editable=False, help_text="Version of the license.") license_url = models.CharField( @@ -660,6 +666,7 @@ def get_community_resource_feature_link_url(self): FieldPanel('ibook_volume_2_isbn_10'), FieldPanel('ibook_volume_2_isbn_13'), FieldPanel('license_text'), + FieldPanel('license_name'), FieldPanel('webview_rex_link'), FieldPanel('rex_callout_title'), FieldPanel('rex_callout_blurb'), @@ -881,6 +888,16 @@ def save(self, *args, **kwargs): if self.support_statement: Book.objects.filter(locale=self.locale).update(support_statement=self.support_statement) + # populate license + if self.license_name: + if self.license_name == CC_BY_LICENSE_NAME: + self.license_url = CC_BY_LICENSE_URL + self.license_version = CC_BY_LICENSE_VERSION + else: + self.license_url = CC_NC_SA_LICENSE_URL + self.license_version = CC_NC_SA_LICENSE_VERSION + + # if book is new, clear out isbn 10 fields if self._state.adding: self.print_isbn_10 = None diff --git a/books/tests.py b/books/tests.py index afca43bd3..8c20a8a17 100644 --- a/books/tests.py +++ b/books/tests.py @@ -1,5 +1,7 @@ from wagtail.tests.utils import WagtailPageTests from wagtail.core.models import Page + +import snippets.models from pages.models import HomePage from books.models import BookIndex, Book from shared.test_utilities import assertPathDoesNotRedirectToTrailingSlash @@ -117,3 +119,21 @@ def test_cannot_create_book_under_homepage(self): def test_slashless_apis_are_good(self): assertPathDoesNotRedirectToTrailingSlash(self, '/apps/cms/api/books') assertPathDoesNotRedirectToTrailingSlash(self, '/apps/cms/api/books/slug') + + def test_can_create_book_with_cc_license(self): + book_index = BookIndex.objects.all()[0] + root_page = Page.objects.get(title="Root") + book = Book(title="University Physics", + slug="university-physics", + cnx_id='031da8d3-b525-429c-80cf-6c8ed997733a', + salesforce_abbreviation='University Phys (Calc)', + salesforce_name='University Physics', + description="Test Book", + cover=self.test_doc, + title_image=self.test_doc, + publish_date=datetime.date.today(), + locale=root_page.locale, + license_name='Creative Commons Attribution License', + ) + book_index.add_child(instance=book) + self.assertEqual(book.license_url, 'https://creativecommons.org/licenses/by/4.0/') diff --git a/snippets/migrations/0020_contentlicense.py b/snippets/migrations/0020_contentlicense.py new file mode 100644 index 000000000..711537a74 --- /dev/null +++ b/snippets/migrations/0020_contentlicense.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.5 on 2022-05-09 15:39 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0066_collection_management_permissions'), + ('snippets', '0019_givebanner'), + ] + + operations = [ + migrations.CreateModel( + name='ContentLicense', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('translation_key', models.UUIDField(default=uuid.uuid4, editable=False)), + ('license_code', models.CharField(blank=True, max_length=255, null=True)), + ('version', models.CharField(blank=True, max_length=255, null=True)), + ('license_name', models.CharField(blank=True, max_length=255, null=True)), + ('license_url', models.URLField(blank=True, null=True)), + ('locale', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale')), + ], + options={ + 'abstract': False, + 'unique_together': {('translation_key', 'locale')}, + }, + ), + ] diff --git a/snippets/migrations/0022_merge_0020_contentlicense_0021_blogcollection.py b/snippets/migrations/0022_merge_0020_contentlicense_0021_blogcollection.py new file mode 100644 index 000000000..5c275d08b --- /dev/null +++ b/snippets/migrations/0022_merge_0020_contentlicense_0021_blogcollection.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.5 on 2022-05-10 15:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('snippets', '0020_contentlicense'), + ('snippets', '0021_blogcollection'), + ] + + operations = [ + ] diff --git a/snippets/migrations/0023_delete_contentlicense.py b/snippets/migrations/0023_delete_contentlicense.py new file mode 100644 index 000000000..43cc75979 --- /dev/null +++ b/snippets/migrations/0023_delete_contentlicense.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.5 on 2022-05-10 21:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('snippets', '0022_merge_0020_contentlicense_0021_blogcollection'), + ] + + operations = [ + migrations.DeleteModel( + name='ContentLicense', + ), + ] diff --git a/snippets/models.py b/snippets/models.py index 219d84a54..5a4bba929 100644 --- a/snippets/models.py +++ b/snippets/models.py @@ -314,4 +314,5 @@ def __str__(self): return self.name -register_snippet(BlogCollection) \ No newline at end of file +register_snippet(BlogCollection) + diff --git a/snippets/serializers.py b/snippets/serializers.py index 5070ec367..7a27742af 100644 --- a/snippets/serializers.py +++ b/snippets/serializers.py @@ -62,3 +62,4 @@ class Meta: fields = ('name', 'description', 'collection_image') + diff --git a/snippets/views.py b/snippets/views.py index 0f74ade1f..4c0e2bcfa 100644 --- a/snippets/views.py +++ b/snippets/views.py @@ -1,7 +1,8 @@ from rest_framework import viewsets from .models import Role, Subject, ErrataContent, SubjectCategory, GiveBanner, BlogContentType, BlogCollection -from .serializers import RoleSerializer, SubjectSerializer, ErrataContentSerializer, SubjectCategorySerializer, GiveBannerSerializer, BlogContentTypeSerializer, BlogCollectionSerializer +from .serializers import RoleSerializer, SubjectSerializer, ErrataContentSerializer, SubjectCategorySerializer, \ + GiveBannerSerializer, BlogContentTypeSerializer, BlogCollectionSerializer from rest_framework import generics, viewsets from django_filters.rest_framework import DjangoFilterBackend