diff --git a/allies/models.py b/allies/models.py
index d51265407..eda56683e 100644
--- a/allies/models.py
+++ b/allies/models.py
@@ -4,7 +4,6 @@
MultiFieldPanel)
from wagtail.fields import RichTextField
from wagtail.models import Page
-from wagtail.images.edit_handlers import ImageChooserPanel
from openstax.functions import build_image_url
from snippets.models import Subject
diff --git a/api/tests.py b/api/tests.py
index 349eb527d..4aefd9370 100644
--- a/api/tests.py
+++ b/api/tests.py
@@ -1,5 +1,5 @@
import json
-from django.test import TestCase, Client
+from django.test import TestCase
from wagtail.test.utils import WagtailTestUtils
from wagtail.images.tests.utils import Image, get_test_image_file
@@ -7,7 +7,7 @@
from api.models import FeatureFlag, WebviewSettings
-from shared.test_utilities import assertPathDoesNotRedirectToTrailingSlash, mock_user_login
+from shared.test_utilities import mock_user_login
class PagesAPI(TestCase, WagtailTestUtils):
def setUp(self):
@@ -18,7 +18,7 @@ def test_api_v2_pages_urls(self):
response = self.client.get('/apps/cms/api/v2/pages/')
self.assertEqual(response.status_code, 200)
- response = self.client.get('/apps/cms/api/v2/pages')
+ response = self.client.get('/apps/cms/api/v2/pages', follow=True)
self.assertEqual(response.status_code, 200)
@@ -160,7 +160,7 @@ def test_sticky_api(self):
self.assertEqual(response.status_code, 200)
def test_errata_resource_api(self):
- response = self.client.get('/apps/cms/api/errata-fields?field=resources')
+ response = self.client.get('/apps/cms/api/errata-fields/?field=resources')
self.assertNotIn('content', 'OpenStax Concept Coach')
self.assertNotIn('content', 'Rover by OpenStax')
self.assertEqual(response.status_code, 200)
diff --git a/books/models.py b/books/models.py
index 091e6e8c0..283580903 100644
--- a/books/models.py
+++ b/books/models.py
@@ -1,34 +1,24 @@
import re
import html
-import json
-import urllib
-import ssl
from sentry_sdk import capture_exception
from django.conf import settings
from django.db import models
-from django.forms import ValidationError
from django.utils.html import format_html, mark_safe
-from django.contrib.postgres.fields import ArrayField
from modelcluster.fields import ParentalKey
-from modelcluster.models import ClusterableModel
from wagtail.admin.panels import (FieldPanel,
- InlinePanel,
- PageChooserPanel,
- StreamFieldPanel)
+ InlinePanel,
+ PageChooserPanel)
+from wagtail.admin.widgets.slug import SlugInput
from wagtail import blocks
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Orderable, Page
-from wagtail.documents.edit_handlers import DocumentChooserPanel
from wagtail.snippets.blocks import SnippetChooserBlock
-from wagtail.images.edit_handlers import ImageChooserPanel
-from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.admin.panels import TabbedInterface, ObjectList
from wagtail.api import APIField
-from wagtail.snippets.models import register_snippet
from wagtail.models import Site
-from openstax.functions import build_document_url, build_image_url
+from openstax.functions import build_document_url
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, K12_CATEGORIES
import snippets.models as snippets
@@ -65,6 +55,7 @@ class VideoFacultyResource(models.Model):
FieldPanel('video_file'),
]
+
class OrientationFacultyResource(models.Model):
resource_heading = models.CharField(max_length=255, null=True)
resource_description = RichTextField(blank=True, null=True)
@@ -134,6 +125,7 @@ def get_document_title(self):
FieldPanel('featured'),
]
+
class FacultyResources(models.Model):
resource = models.ForeignKey(
snippets.FacultyResource,
@@ -145,25 +137,31 @@ class FacultyResources(models.Model):
def get_resource_heading(self):
return self.resource.heading
+
resource_heading = property(get_resource_heading)
def get_resource_description(self):
return self.resource.description
+
resource_description = property(get_resource_description)
def get_resource_unlocked(self):
return self.resource.unlocked_resource
+
resource_unlocked = property(get_resource_unlocked)
def get_resource_icon(self):
return self.resource.resource_icon
+
resource_icon = property(get_resource_icon)
def get_resource_creator_fest_resource(self):
return self.resource.creator_fest_resource
+
creator_fest_resource = property(get_resource_creator_fest_resource)
- link_external = models.URLField("External link", default='', blank=True, help_text="Provide an external URL starting with https:// (or fill out either one of the following two).")
+ link_external = models.URLField("External link", default='', blank=True,
+ help_text="Provide an external URL starting with https:// (or fill out either one of the following two).")
link_page = models.ForeignKey(
'wagtailcore.Page',
null=True,
@@ -183,14 +181,17 @@ def get_resource_creator_fest_resource(self):
def get_link_document(self):
return build_document_url(self.link_document.url)
+
link_document_url = property(get_link_document)
def get_document_title(self):
return self.link_document.title
+
link_document_title = property(get_document_title)
link_text = models.CharField(max_length=255, null=True, blank=True, help_text="Call to Action Text")
- coming_soon_text = models.CharField(max_length=255, null=True, blank=True, help_text="If there is text in this field a coming soon banner will be added with this description.")
+ coming_soon_text = models.CharField(max_length=255, null=True, blank=True,
+ help_text="If there is text in this field a coming soon banner will be added with this description.")
video_reference_number = models.IntegerField(blank=True, null=True)
updated = models.DateTimeField(blank=True, null=True, help_text='Late date resource was updated')
featured = models.BooleanField(default=False, help_text="Add to featured bar on resource page")
@@ -200,6 +201,7 @@ def get_document_title(self):
def get_resource_category(self):
return self.resource.resource_category
+
resource_category = property(get_resource_category)
api_fields = [
@@ -250,21 +252,26 @@ class StudentResources(models.Model):
def get_resource_heading(self):
return self.resource.heading
+
resource_heading = property(get_resource_heading)
def get_resource_description(self):
return self.resource.description
+
resource_description = property(get_resource_description)
def get_resource_unlocked(self):
return self.resource.unlocked_resource
+
resource_unlocked = property(get_resource_unlocked)
-
+
def get_resource_icon(self):
return self.resource.resource_icon
+
resource_icon = property(get_resource_icon)
- link_external = models.URLField("External link", default='', blank=True, help_text="Provide an external URL starting with http:// (or fill out either one of the following two).")
+ link_external = models.URLField("External link", default='', blank=True,
+ help_text="Provide an external URL starting with http:// (or fill out either one of the following two).")
link_page = models.ForeignKey(
'wagtailcore.Page',
null=True,
@@ -284,22 +291,25 @@ def get_resource_icon(self):
def get_link_document(self):
return build_document_url(self.link_document.url)
+
link_document_url = property(get_link_document)
def get_document_title(self):
return self.link_document.title
+
link_document_title = property(get_document_title)
link_text = models.CharField(max_length=255, null=True, blank=True, help_text="Call to Action Text")
- coming_soon_text = models.CharField(max_length=255, null=True, blank=True, help_text="If there is text in this field a coming soon banner will be added with this description.")
+ coming_soon_text = models.CharField(max_length=255, null=True, blank=True,
+ help_text="If there is text in this field a coming soon banner will be added with this description.")
updated = models.DateTimeField(blank=True, null=True, help_text='Late date resource was updated')
print_link = models.URLField(blank=True, null=True, help_text="Link for Buy Print link on resource")
display_on_k12 = models.BooleanField(default=False, help_text="Display resource on K12 subject pages")
def get_resource_category(self):
return self.resource.resource_category
- resource_category = property(get_resource_category)
+ resource_category = property(get_resource_category)
api_fields = [
APIField('resource_heading'),
@@ -333,9 +343,12 @@ def get_resource_category(self):
class Authors(models.Model):
name = models.CharField(max_length=255, help_text="Full name of the author.")
- university = models.CharField(max_length=255, null=True, blank=True, help_text="Name of the university/institution the author is associated with.")
- country = models.CharField(max_length=255, null=True, blank=True, help_text="Country of the university/institution.")
- senior_author = models.BooleanField(default=False, help_text="Whether the author is a senior author. (Senior authors are shown before non-senior authors.)")
+ university = models.CharField(max_length=255, null=True, blank=True,
+ help_text="Name of the university/institution the author is associated with.")
+ country = models.CharField(max_length=255, null=True, blank=True,
+ help_text="Country of the university/institution.")
+ senior_author = models.BooleanField(default=False,
+ help_text="Whether the author is a senior author. (Senior authors are shown before non-senior authors.)")
display_at_top = models.BooleanField(default=False, help_text="Whether display the author on top.")
book = ParentalKey(
'books.Book', related_name='book_contributing_authors', null=True, blank=True)
@@ -358,14 +371,16 @@ class Authors(models.Model):
class AuthorBlock(blocks.StructBlock):
- name = blocks.CharBlock(required=True, help_text="Full name of the author.")
- university = blocks.CharBlock(required=False, help_text="Name of the university/institution the author is associated with.")
- country = blocks.CharBlock(required=False, help_text="Country of the university/institution.")
- senior_author = blocks.BooleanBlock(required=False, help_text="Whether the author is a senior author. (Senior authors are shown before non-senior authors.)")
- display_at_top = blocks.BooleanBlock(required=False, help_text="Whether display the author on top.")
+ name = blocks.CharBlock(required=True, help_text="Full name of the author.")
+ university = blocks.CharBlock(required=False,
+ help_text="Name of the university/institution the author is associated with.")
+ country = blocks.CharBlock(required=False, help_text="Country of the university/institution.")
+ senior_author = blocks.BooleanBlock(required=False,
+ help_text="Whether the author is a senior author. (Senior authors are shown before non-senior authors.)")
+ display_at_top = blocks.BooleanBlock(required=False, help_text="Whether display the author on top.")
- class Meta:
- icon = 'user'
+ class Meta:
+ icon = 'user'
class SubjectBooks(models.Model):
@@ -373,18 +388,22 @@ class SubjectBooks(models.Model):
def get_subject_name(self):
return self.subject.name
+
subject_name = property(get_subject_name)
def get_subject_page_content(self):
return self.subject.page_content
+
subject_page_content = property(get_subject_page_content)
def get_subject_page_title(self):
return self.subject.seo_title
+
subject_seo_title = property(get_subject_page_title)
def get_subject_meta(self):
return self.subject.search_description
+
subject_search_description = property(get_subject_meta)
api_fields = [
@@ -393,15 +412,19 @@ def get_subject_meta(self):
APIField('subject_search_description')
]
+
class K12SubjectBooks(models.Model):
- subject = models.ForeignKey(snippets.K12Subject, on_delete=models.SET_NULL, null=True, related_name='k12subjects_subject')
+ subject = models.ForeignKey(snippets.K12Subject, on_delete=models.SET_NULL, null=True,
+ related_name='k12subjects_subject')
def get_subject_name(self):
return self.subject.name
+
subject_name = property(get_subject_name)
def get_subject_category(self):
return self.subject.subject_category
+
subject_category = property(get_subject_category)
api_fields = [
@@ -411,14 +434,17 @@ def get_subject_category(self):
class BookCategory(models.Model):
- category = models.ForeignKey(snippets.SubjectCategory, on_delete=models.SET_NULL, null=True, related_name='subjects_subjectcategory')
+ category = models.ForeignKey(snippets.SubjectCategory, on_delete=models.SET_NULL, null=True,
+ related_name='subjects_subjectcategory')
def get_subject_name(self):
return self.category.subject_name
+
subject_name = property(get_subject_name)
def get_subject_category(self):
return self.category.subject_category if self.category is not None else ''
+
subject_category = property(get_subject_category)
api_fields = [
@@ -472,18 +498,23 @@ class Meta:
class BookFacultyResources(Orderable, FacultyResources):
book_faculty_resource = ParentalKey('books.Book', related_name='book_faculty_resources')
+
class VideoFacultyResources(Orderable, VideoFacultyResource):
book_video_faculty_resource = ParentalKey('books.Book', related_name='book_video_faculty_resources')
+
class OrientationFacultyResources(Orderable, OrientationFacultyResource):
book_orientation_faculty_resource = ParentalKey('books.Book', related_name='book_orientation_faculty_resources')
+
class BookStudentResources(Orderable, StudentResources):
book_student_resource = ParentalKey('books.Book', related_name='book_student_resources')
+
class BookSubjects(Orderable, SubjectBooks):
book_subject = ParentalKey('books.Book', related_name='book_subjects')
+
class K12BookSubjects(Orderable, K12SubjectBooks):
k12book_subject = ParentalKey('books.Book', related_name='k12book_subjects')
@@ -499,7 +530,8 @@ class Book(Page):
)
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.')
+ book_state = models.CharField(max_length=255, choices=BOOK_STATES, default='live',
+ help_text='The state of the book.')
cnx_id = models.CharField(
max_length=255, help_text="collection.xml UUID. Should be same as book UUID",
blank=True, null=True)
@@ -509,7 +541,7 @@ class Book(Page):
salesforce_abbreviation = models.CharField(max_length=255, blank=True, null=True)
salesforce_name = models.CharField(max_length=255, blank=True, null=True)
salesforce_book_id = models.CharField(max_length=255, blank=True, null=True,
- help_text='No tracking and not included on adoption and interest forms if left blank)')
+ help_text='No tracking and not included on adoption and interest forms if left blank)')
updated = models.DateTimeField(blank=True, null=True, help_text='Late date web content was updated')
is_ap = models.BooleanField(default=False, help_text='Whether this book is an AP (Advanced Placement) book.')
description = RichTextField(
@@ -522,11 +554,13 @@ class Book(Page):
related_name='+',
help_text='The book cover to be shown on the website.'
)
+
def get_cover_url(self):
if self.cover:
return build_document_url(self.cover.url)
else:
return ''
+
cover_url = property(get_cover_url)
title_image = models.ForeignKey(
@@ -536,27 +570,35 @@ def get_cover_url(self):
related_name='+',
help_text='The svg for title image to be shown on the website.'
)
+
def get_title_image_url(self):
return build_document_url(self.title_image.url)
+
title_image_url = property(get_title_image_url)
- cover_color = models.CharField(max_length=255, choices=COVER_COLORS, default='blue', help_text='The color of the cover.')
- book_cover_text_color = models.CharField(max_length=255, choices=BOOK_COVER_TEXT_COLOR, default='yellow', help_text="Use by the Unified team - this will not change the text color on the book cover.")
+ cover_color = models.CharField(max_length=255, choices=COVER_COLORS, default='blue',
+ help_text='The color of the cover.')
+ book_cover_text_color = models.CharField(max_length=255, choices=BOOK_COVER_TEXT_COLOR, default='yellow',
+ help_text="Use by the Unified team - this will not change the text color on the book cover.")
reverse_gradient = models.BooleanField(default=False)
publish_date = models.DateField(null=True, help_text='Date the book is published on.')
authors = StreamField([
('author', AuthorBlock()),
], null=True, use_json_field=True)
- print_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for print version (hardcover).')
- print_softcover_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for print version (softcover).')
+ print_isbn_13 = models.CharField(max_length=255, blank=True, null=True,
+ help_text='ISBN 13 for print version (hardcover).')
+ print_softcover_isbn_13 = models.CharField(max_length=255, blank=True, null=True,
+ help_text='ISBN 13 for print version (softcover).')
digital_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for digital version.')
ibook_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for iBook version.')
- ibook_volume_2_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for iBook v2 version.')
+ ibook_volume_2_isbn_13 = models.CharField(max_length=255, blank=True, null=True,
+ help_text='ISBN 13 for iBook v2 version.')
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, choices=licenses,default=CC_BY_LICENSE_NAME, 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(
@@ -570,11 +612,13 @@ def get_title_image_url(self):
related_name='+',
help_text="High quality PDF document of the book."
)
+
def get_high_res_pdf_url(self):
if self.high_resolution_pdf:
return build_document_url(self.high_resolution_pdf.url)
else:
return None
+
high_resolution_pdf_url = property(get_high_res_pdf_url)
low_resolution_pdf = models.ForeignKey(
@@ -585,16 +629,22 @@ def get_high_res_pdf_url(self):
related_name='+',
help_text="Low quality PDF document of the book."
)
+
def get_low_res_pdf_url(self):
if self.low_resolution_pdf:
return build_document_url(self.low_resolution_pdf.url)
else:
return None
+
low_resolution_pdf_url = property(get_low_res_pdf_url)
- free_stuff_instructor = StreamField(SharedContentBlock(), null=True, blank=True, help_text="Snippet to show texts for free instructor resources.", use_json_field=True)
- free_stuff_student = StreamField(SharedContentBlock(), null=True, blank=True, help_text="Snipped to show texts for free student resources.", use_json_field=True)
- community_resource_heading = models.CharField(max_length=255, blank=True, null=True, help_text="Snipped to show texts for community resources.")
+ free_stuff_instructor = StreamField(SharedContentBlock(), null=True, blank=True,
+ help_text="Snippet to show texts for free instructor resources.",
+ use_json_field=True)
+ free_stuff_student = StreamField(SharedContentBlock(), null=True, blank=True,
+ help_text="Snipped to show texts for free student resources.", use_json_field=True)
+ community_resource_heading = models.CharField(max_length=255, blank=True, null=True,
+ help_text="Snipped to show texts for community resources.")
community_resource_logo = models.ForeignKey(
'wagtaildocs.Document',
null=True,
@@ -622,6 +672,7 @@ def get_community_resource_logo_url(self):
related_name='+',
help_text='Document of the community resource feature.'
)
+
def get_community_resource_feature_link_url(self):
return build_document_url(self.community_resource_feature_link.url)
@@ -633,9 +684,12 @@ def get_community_resource_feature_link_url(self):
ibook_link_volume_2 = models.URLField(blank=True, help_text="Link to secondary iBook")
webview_link = models.URLField(blank=True, help_text="Link to CNX Webview book")
webview_rex_link = models.URLField(blank=True, help_text="Link to REX Webview book")
- rex_callout_title = models.CharField(max_length=255, blank=True, null=True, help_text='Title of the REX callout', default="Recommended")
- rex_callout_blurb = models.CharField(max_length=255, blank=True, null=True, help_text='Additional text for the REX callout.')
- enable_study_edge = models.BooleanField(default=False, help_text="This will cause the link to the Study Edge app appear on the book details page.")
+ rex_callout_title = models.CharField(max_length=255, blank=True, null=True, help_text='Title of the REX callout',
+ default="Recommended")
+ rex_callout_blurb = models.CharField(max_length=255, blank=True, null=True,
+ help_text='Additional text for the REX callout.')
+ enable_study_edge = models.BooleanField(default=False,
+ help_text="This will cause the link to the Study Edge app appear on the book details page.")
bookshare_link = models.URLField(blank=True, help_text="Link to Bookshare resources")
amazon_coming_soon = models.BooleanField(default=False, verbose_name="Individual Print Coming Soon")
amazon_link = models.URLField(blank=True, verbose_name="Individual Print Link")
@@ -643,23 +697,37 @@ def get_community_resource_feature_link_url(self):
kindle_link = models.URLField(blank=True, help_text="Link to Kindle version")
chegg_link = models.URLField(blank=True, null=True, help_text="Link to Chegg e-reader")
chegg_link_text = models.CharField(max_length=255, blank=True, null=True, help_text='Text for Chegg link.')
- bookstore_coming_soon = models.BooleanField(default=False, help_text='Whether this book is coming to bookstore soon.')
- bookstore_content = StreamField(SharedContentBlock(), null=True, blank=True, help_text='Bookstore content.', use_json_field=True)
+ bookstore_coming_soon = models.BooleanField(default=False,
+ help_text='Whether this book is coming to bookstore soon.')
+ bookstore_content = StreamField(SharedContentBlock(), null=True, blank=True, help_text='Bookstore content.',
+ use_json_field=True)
comp_copy_available = models.BooleanField(default=True, help_text='Whether free compy available for teachers.')
- comp_copy_content = StreamField(SharedContentBlock(), null=True, blank=True, help_text='Content of the free copy.', use_json_field=True)
+ comp_copy_content = StreamField(SharedContentBlock(), null=True, blank=True, help_text='Content of the free copy.',
+ use_json_field=True)
tutor_marketing_book = models.BooleanField(default=False, help_text='Whether this is a Tutor marketing book.')
assignable_book = models.BooleanField(default=False, help_text='Whether this is an Assignable book.')
- partner_list_label = models.CharField(max_length=255, null=True, blank=True, help_text="Controls the heading text on the book detail page for partners. This will update ALL books to use this value!")
- partner_page_link_text = models.CharField(max_length=255, null=True, blank=True, help_text="Link to partners page on top right of list.")
- featured_resources_header = models.CharField(max_length=255, null=True, blank=True, help_text="Featured resource header on instructor resources tab.")
- customization_form_heading = models.CharField(max_length=255, null=True, blank=True, help_text="Heading for the CE customization form. This will update ALL books to use this value!", default="Customization Form")
- customization_form_subheading = models.CharField(max_length=255, null=True, blank=True, help_text="Subheading for the CE customization form. This will update ALL books to use this value!", default="Please select the modules (up to 10), that you want to customize with Google Docs.")
- customization_form_disclaimer = RichTextField(blank=True, help_text="This will update ALL books to use this value!", default="
Disclaimer
The following features and functionality are not available to teachers and students using Google Docs customized content:
Errata updates. OpenStax webview is updated at least twice yearly. Customized Google Docs will not receive these content updates.
Access to study tools. OpenStax webview has in-book search, highlighting, study guides, and more available for free. This functionality will not be available in Google Docs versions.
Formatting. Print books and webview have a specific design and structure format developed for those platforms. These functionalities are not available in the Google Docs versions.
")
- customization_form_next_steps = RichTextField(blank=True, help_text="This will update ALL books to use this value!", default="
Next Steps
Within two business days, you will receive an email for each module that you have requested access to customize.
The link provided in the email will be your own copy of the Google Doc that OpenStax generated for you.
Once you have accessessed the document you can make the changes you desire and share with your students. We recommend using the "Publish to the Web" functionality under the file menu for sharing with students.
")
+ partner_list_label = models.CharField(max_length=255, null=True, blank=True,
+ help_text="Controls the heading text on the book detail page for partners. This will update ALL books to use this value!")
+ partner_page_link_text = models.CharField(max_length=255, null=True, blank=True,
+ help_text="Link to partners page on top right of list.")
+ featured_resources_header = models.CharField(max_length=255, null=True, blank=True,
+ help_text="Featured resource header on instructor resources tab.")
+ customization_form_heading = models.CharField(max_length=255, null=True, blank=True,
+ help_text="Heading for the CE customization form. This will update ALL books to use this value!",
+ default="Customization Form")
+ customization_form_subheading = models.CharField(max_length=255, null=True, blank=True,
+ help_text="Subheading for the CE customization form. This will update ALL books to use this value!",
+ default="Please select the modules (up to 10), that you want to customize with Google Docs.")
+ customization_form_disclaimer = RichTextField(blank=True, help_text="This will update ALL books to use this value!",
+ default="
Disclaimer
The following features and functionality are not available to teachers and students using Google Docs customized content:
Errata updates. OpenStax webview is updated at least twice yearly. Customized Google Docs will not receive these content updates.
Access to study tools. OpenStax webview has in-book search, highlighting, study guides, and more available for free. This functionality will not be available in Google Docs versions.
Formatting. Print books and webview have a specific design and structure format developed for those platforms. These functionalities are not available in the Google Docs versions.
")
+ customization_form_next_steps = RichTextField(blank=True, help_text="This will update ALL books to use this value!",
+ default="
Next Steps
Within two business days, you will receive an email for each module that you have requested access to customize.
The link provided in the email will be your own copy of the Google Doc that OpenStax generated for you.
Once you have accessessed the document you can make the changes you desire and share with your students. We recommend using the "Publish to the Web" functionality under the file menu for sharing with students.
")
adoptions = models.IntegerField(blank=True, null=True)
savings = models.IntegerField(blank=True, null=True)
- support_statement = models.TextField(blank=True, null=True, default="With philanthropic support, this book is used in classrooms, saving students dollars this school year. Learn more about our impact and how you can help.", help_text="Updating this statement updates it for all book pages.")
-
+ support_statement = models.TextField(blank=True, null=True,
+ default="With philanthropic support, this book is used in classrooms, saving students dollars this school year. Learn more about our impact and how you can help.",
+ help_text="Updating this statement updates it for all book pages.")
+
promote_snippet = StreamField(PromoteSnippetBlock(), null=True, blank=True, use_json_field=True)
videos = StreamField([
@@ -684,7 +752,8 @@ def get_community_resource_feature_link_url(self):
])))
], null=True, blank=True, use_json_field=True)
- last_updated_pdf = models.DateTimeField(blank=True, null=True, help_text="Last time PDF was revised.", verbose_name='PDF Content Revision Date')
+ last_updated_pdf = models.DateTimeField(blank=True, null=True, help_text="Last time PDF was revised.",
+ verbose_name='PDF Content Revision Date')
book_detail_panel = Page.content_panels + [
FieldPanel('book_state'),
@@ -911,14 +980,14 @@ def book_urls(self):
book_urls = []
for field in self.api_fields:
try:
- url = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', getattr(self, field))
+ url = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',
+ getattr(self, field))
if url:
book_urls.append(url)
except(TypeError, AttributeError):
pass
return book_urls
-
def save(self, *args, **kwargs):
if self.cnx_id:
self.webview_link = '{}contents/{}'.format(settings.CNX_URL, self.cnx_id)
@@ -933,11 +1002,14 @@ def save(self, *args, **kwargs):
if self.customization_form_heading:
Book.objects.filter(locale=self.locale).update(customization_form_heading=self.customization_form_heading)
if self.customization_form_subheading:
- Book.objects.filter(locale=self.locale).update(customization_form_subheading=self.customization_form_subheading)
+ Book.objects.filter(locale=self.locale).update(
+ customization_form_subheading=self.customization_form_subheading)
if self.customization_form_disclaimer:
- Book.objects.filter(locale=self.locale).update(customization_form_disclaimer=self.customization_form_disclaimer)
+ Book.objects.filter(locale=self.locale).update(
+ customization_form_disclaimer=self.customization_form_disclaimer)
if self.customization_form_next_steps:
- Book.objects.filter(locale=self.locale).update(customization_form_next_steps=self.customization_form_next_steps)
+ Book.objects.filter(locale=self.locale).update(
+ customization_form_next_steps=self.customization_form_next_steps)
if self.support_statement:
Book.objects.filter(locale=self.locale).update(support_statement=self.support_statement)
@@ -959,7 +1031,6 @@ def save(self, *args, **kwargs):
return super(Book, self).save(*args, **kwargs)
-
def get_url_parts(self, *args, **kwargs):
# This overrides the "Live" link in admin to take you to proper FE page
url_parts = super(Book, self).get_url_parts(*args, **kwargs)
@@ -972,7 +1043,6 @@ def get_url_parts(self, *args, **kwargs):
return (site_id, root_url, page_path)
-
def get_sitemap_urls(self, request=None):
return [
{
@@ -1000,7 +1070,8 @@ class BookIndex(Page):
dev_standard_3_description = RichTextField()
dev_standard_4_heading = models.CharField(
max_length=255, blank=True, null=True)
- dev_standard_4_description = models.TextField(help_text="Keep in place to populate with Salesforce data. id=adoption_number for classrooms and id=savings for savings number.")
+ dev_standard_4_description = models.TextField(
+ help_text="Keep in place to populate with Salesforce data. id=adoption_number for classrooms and id=savings for savings number.")
subject_list_heading = models.CharField(
max_length=255, blank=True, null=True)
promote_image = models.ForeignKey(
@@ -1019,7 +1090,7 @@ class BookIndex(Page):
@property
def books(self):
- books = Book.objects.live().filter(locale=self.locale).exclude(book_state='unlisted').order_by('title')
+ books = Book.objects.live().filter(locale=self.locale).exclude(book_state='unlisted').order_by('title')
book_data = []
for book in books:
has_faculty_resources = BookFacultyResources.objects.filter(book_faculty_resource=book).exists()
@@ -1076,7 +1147,7 @@ def books(self):
]
promote_panels = [
- FieldPanel('slug'),
+ FieldPanel('slug', widget=SlugInput),
FieldPanel('seo_title'),
FieldPanel('search_description'),
FieldPanel('promote_image')
diff --git a/errata/models.py b/errata/models.py
index c1f12cf09..c59b2c9ce 100644
--- a/errata/models.py
+++ b/errata/models.py
@@ -276,7 +276,7 @@ def save(self, *args, **kwargs):
@hooks.register('register_admin_menu_item')
def register_errata_menu_item():
- return MenuItem('Errata', '/django-admin/errata/errata', classnames='icon icon-form', order=10000)
+ return MenuItem('Errata', '/django-admin/errata/errata', classname='icon icon-form', order=10000)
def __str__(self):
return self.book.book_title
diff --git a/extraadminfilters/filters.py b/extraadminfilters/filters.py
index ebb78cc0a..c0a6d8b1a 100644
--- a/extraadminfilters/filters.py
+++ b/extraadminfilters/filters.py
@@ -1,6 +1,4 @@
from django.contrib.admin.filters import FieldListFilter
-from django.db.models.fields import IntegerField, AutoField
-from django.db.models.fields.related import OneToOneField, ForeignKey, ManyToOneRel
class MultipleSelectFieldListFilter(FieldListFilter):
diff --git a/global_settings/wagtail_hooks.py b/global_settings/wagtail_hooks.py
index cff2ebd74..904a10250 100644
--- a/global_settings/wagtail_hooks.py
+++ b/global_settings/wagtail_hooks.py
@@ -35,9 +35,9 @@ def register_strikethrough_feature(features):
@hooks.register('register_settings_menu_item')
def register_500_menu_item():
- return MenuItem('Generate 500', reverse('throw_error'), classnames='icon icon-warning', order=10000)
+ return MenuItem('Generate 500', reverse('throw_error'), classname='icon icon-warning', order=10000)
@hooks.register('register_settings_menu_item')
def register_clear_cache_menu_item():
- return MenuItem('Clear Cloudfront Cache', reverse('clear_entire_cache'), classnames='icon icon-bin', order=11000)
+ return MenuItem('Clear Cloudfront Cache', reverse('clear_entire_cache'), classname='icon icon-bin', order=11000)
diff --git a/locked-requirements.txt b/locked-requirements.txt
deleted file mode 100644
index 34148b120..000000000
--- a/locked-requirements.txt
+++ /dev/null
@@ -1,242 +0,0 @@
-appdirs==1.4.4
-chardet==4.0.0
-django-admin-rangefilter==0.8.4
-django-crontab==0.7.1
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
-django_debug_toolbar==3.8.1
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- sqlparse==0.4.3
-django-extensions==3.2.1
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
-django-import-export==2.8.0
- diff-match-patch==20200713
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- tablib==3.3.0
-django-libsass==0.9
- django-compressor==4.3.1
- django-appconf==1.0.5
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- rcssmin==1.1.1
- rjsmin==1.2.1
- libsass==0.22.0
-django-rest-auth==0.9.5
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- djangorestframework==3.14.0
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- pytz==2022.7.1
- six==1.16.0
-django-reversion==5.0.0
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
-django-ses==3.0.1
- boto3==1.26.56
- botocore==1.29.56
- jmespath==1.0.1
- python-dateutil==2.8.2
- six==1.16.0
- urllib3==1.26.14
- jmespath==1.0.1
- s3transfer==0.6.0
- botocore==1.29.56
- jmespath==1.0.1
- python-dateutil==2.8.2
- six==1.16.0
- urllib3==1.26.14
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- pytz==2022.7.1
-django-storages==1.12.3
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
-future==0.18.2
-html2text==2020.1.16
-jsonfield==3.1.0
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
-mapbox==0.18.1
- boto3==1.26.56
- botocore==1.29.56
- jmespath==1.0.1
- python-dateutil==2.8.2
- six==1.16.0
- urllib3==1.26.14
- jmespath==1.0.1
- s3transfer==0.6.0
- botocore==1.29.56
- jmespath==1.0.1
- python-dateutil==2.8.2
- six==1.16.0
- urllib3==1.26.14
- CacheControl==0.12.11
- msgpack==1.0.4
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
- iso3166==2.1.1
- polyline==2.0.0
- python-dateutil==2.8.2
- six==1.16.0
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
- uritemplate==4.1.1
-MarkupPy==1.14
-odfpy==1.4.1
- defusedxml==0.7.1
-openpyxl==3.0.10
- et-xmlfile==1.1.0
-pip==22.3.1
-pipdeptree==2.7.0
-psycopg2==2.9.5
-pycryptodome==3.14.1
-PyJWE==1.0.0
- cryptography==39.0.0
- cffi==1.15.1
- pycparser==2.21
-sentry-sdk==1.15.0
- certifi==2022.12.7
- urllib3==1.26.14
-setuptools==65.5.1
-simple-salesforce==1.11.6
- Authlib==1.2.0
- cryptography==39.0.0
- cffi==1.15.1
- pycparser==2.21
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
- zeep==4.2.1
- attrs==22.2.0
- isodate==0.6.1
- six==1.16.0
- lxml==4.9.2
- platformdirs==2.6.2
- pytz==2022.7.1
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
- requests-file==1.5.1
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
- six==1.16.0
- requests-toolbelt==0.10.1
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
-social-auth-app-django==5.0.0
- social-auth-core==4.3.0
- cryptography==39.0.0
- cffi==1.15.1
- pycparser==2.21
- defusedxml==0.7.1
- oauthlib==3.2.2
- PyJWT==2.6.0
- python3-openid==3.2.0
- defusedxml==0.7.1
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
- requests-oauthlib==1.3.1
- oauthlib==3.2.2
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
-ua-parser==0.16.1
-unicodecsv==0.14.1
-Unidecode==1.3.4
-vcrpy==4.1.1
- PyYAML==6.0
- six==1.16.0
- wrapt==1.14.1
- yarl==1.8.2
- idna==3.4
- multidict==6.0.4
-wagtail==4.0.4
- anyascii==0.3.1
- beautifulsoup4==4.9.3
- soupsieve==2.3.2.post1
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- django-filter==21.1
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- django-modelcluster==6.0
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- pytz==2022.7.1
- django-permissionedforms==0.1
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- django-taggit==3.1.0
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- django-treebeard==4.6.0
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- djangorestframework==3.14.0
- Django==4.1.7
- asgiref==3.6.0
- sqlparse==0.4.3
- pytz==2022.7.1
- draftjs-exporter==2.1.7
- html5lib==1.1
- six==1.16.0
- webencodings==0.5.1
- l18n==2021.3
- pytz==2022.7.1
- six==1.16.0
- Pillow==9.4.0
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.0.1
- idna==3.4
- urllib3==1.26.14
- tablib==3.3.0
- telepath==0.3
- Willow==1.4.1
- XlsxWriter==3.0.7
-Wand==0.6.7
-whitenoise==6.1.0
-xlrd==2.0.1
-xlwt==1.3.0
diff --git a/news/models.py b/news/models.py
index 3a875822f..a222ba9ff 100644
--- a/news/models.py
+++ b/news/models.py
@@ -1,5 +1,3 @@
-import json
-
from bs4 import BeautifulSoup
from django.db import models
@@ -7,9 +5,8 @@
from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField, StreamField
-from wagtail.admin.panels import FieldPanel, StreamFieldPanel, InlinePanel
-from wagtail.images.edit_handlers import ImageChooserPanel
-from wagtail.documents.edit_handlers import DocumentChooserPanel
+from wagtail.admin.panels import FieldPanel, InlinePanel
+from wagtail.admin.widgets.slug import SlugInput
from wagtail.embeds.blocks import EmbedBlock
from wagtail.search import index
from wagtail import blocks
@@ -17,8 +14,6 @@
from wagtail.images.blocks import ImageChooserBlock
from wagtail.documents.blocks import DocumentChooserBlock
from wagtail.snippets.blocks import SnippetChooserBlock
-from wagtail.snippets.edit_handlers import SnippetChooserPanel
-from wagtail.snippets.models import register_snippet
from wagtail.api import APIField
from wagtail.images.api.fields import ImageRenditionField
from wagtail.models import Site
@@ -171,7 +166,7 @@ class NewsIndex(Page):
]
promote_panels = [
- FieldPanel('slug'),
+ FieldPanel('slug', widget=SlugInput),
FieldPanel('seo_title'),
FieldPanel('search_description'),
FieldPanel('promote_image')
@@ -385,7 +380,7 @@ def blog_collections(self):
]
promote_panels = [
- FieldPanel('slug'),
+ FieldPanel('slug', widget=SlugInput),
FieldPanel('seo_title'),
FieldPanel('search_description'),
FieldPanel('promote_image')
@@ -592,7 +587,7 @@ def releases(self):
]
promote_panels = [
- FieldPanel('slug'),
+ FieldPanel('slug', widget=SlugInput),
FieldPanel('seo_title'),
FieldPanel('search_description'),
FieldPanel('promote_image')
@@ -681,7 +676,7 @@ def get_sitemap_urls(self, request=None):
]
promote_panels = [
- FieldPanel('slug'),
+ FieldPanel('slug', widget=SlugInput),
FieldPanel('seo_title'),
FieldPanel('search_description'),
FieldPanel('promote_image')
diff --git a/openstax/middleware.py b/openstax/middleware.py
index b1cfb9b2e..1e8306a20 100644
--- a/openstax/middleware.py
+++ b/openstax/middleware.py
@@ -1,19 +1,17 @@
-from django.http import HttpResponsePermanentRedirect
+from django.http import HttpResponsePermanentRedirect, HttpResponse
from django.core.handlers.base import BaseHandler
from django.middleware.common import CommonMiddleware
+from django.utils.http import escape_leading_slashes
from django.conf import settings
+
from ua_parser import user_agent_parser
-from wagtail.models import Page
-from django.shortcuts import get_object_or_404
-from django.template.response import TemplateResponse
-from django.http import HttpResponse
from urllib.parse import unquote
from api.models import FeatureFlag
from books.models import Book, BookIndex
from openstax.functions import build_image_url
from news.models import NewsArticle
-from pages.models import HomePage, Supporters, GeneralPage, PrivacyPolicy, K12Subject, Subject, Subjects
+from pages.models import HomePage, Supporters, PrivacyPolicy, K12Subject, Subject, Subjects
class HttpSmartRedirectResponse(HttpResponsePermanentRedirect):
@@ -22,41 +20,53 @@ class HttpSmartRedirectResponse(HttpResponsePermanentRedirect):
class CommonMiddlewareAppendSlashWithoutRedirect(CommonMiddleware):
""" This class converts HttpSmartRedirectResponse to the common response
- of Django view, without redirect.
+ of Django view, without redirect. This is necessary to match status_codes
+ for urls like /url?q=1 and /url/?q=1. If you don't use it, you will have 302
+ code always on pages without slash.
"""
response_redirect_class = HttpSmartRedirectResponse
- def __init__(self, *args, **kwargs):
- # create django request resolver
- self.handler = BaseHandler()
+def __init__(self, *args, **kwargs):
+ # create django request resolver
+ self.handler = BaseHandler()
+
+ # prevent recursive includes
+ old = settings.MIDDLEWARE
+ name = self.__module__ + '.' + self.__class__.__name__
+ settings.MIDDLEWARE = [i for i in settings.MIDDLEWARE if i != name]
- # prevent recursive includes
- old = settings.MIDDLEWARE
- name = self.__module__ + '.' + self.__class__.__name__
- settings.MIDDLEWARE = [i for i in settings.MIDDLEWARE if i != name]
+ self.handler.load_middleware()
- self.handler.load_middleware()
+ settings.MIDDLEWARE = old
+ super(CommonMiddlewareAppendSlashWithoutRedirect, self).__init__(*args, **kwargs)
- settings.MIDDLEWARE = old
- super(CommonMiddlewareAppendSlashWithoutRedirect, self).__init__(*args, **kwargs)
+def get_full_path_with_slash(self, request):
+ """ Return the full path of the request with a trailing slash appended
+ without Exception in Debug mode
+ """
+ new_path = request.get_full_path(force_append_slash=True)
+ # Prevent construction of scheme relative urls.
+ new_path = escape_leading_slashes(new_path)
+ return new_path
- def process_response(self, request, response):
- response = super(CommonMiddlewareAppendSlashWithoutRedirect, self).process_response(request, response)
+def process_response(self, request, response):
+ response = super(CommonMiddlewareAppendSlashWithoutRedirect, self).process_response(request, response)
- if isinstance(response, HttpSmartRedirectResponse):
- if not request.path.endswith('/'):
- request.path = request.path + '/'
- # we don't need query string in path_info because it's in request.GET already
- request.path_info = request.path
- response = self.handler.get_response(request)
+ if isinstance(response, HttpSmartRedirectResponse):
+ if not request.path.endswith('/'):
+ request.path = request.path + '/'
+ # we don't need query string in path_info because it's in request.GET already
+ request.path_info = request.path
+ response = self.handler.get_response(request)
- return response
+ return response
class CommonMiddlewareOpenGraphRedirect(CommonMiddleware):
OG_USER_AGENTS = [
'twitterbot',
'facebookbot',
+ 'facebookexternalhit/1.1',
'pinterestbot',
'slackbot-linkexpanding',
]
@@ -163,4 +173,3 @@ def page_by_slug(self, page_slug):
-
diff --git a/openstax/settings/base.py b/openstax/settings/base.py
index a3a198d4a..eca84ebf6 100644
--- a/openstax/settings/base.py
+++ b/openstax/settings/base.py
@@ -25,7 +25,7 @@
# These should both be set to true. The openstax.middleware will handle resolving the URL
# without a redirect if needed.
APPEND_SLASH = True
-WAGTAIL_APPEND_SLASH=True
+WAGTAIL_APPEND_SLASH = True
# urls.W002 warns about slashes at the start of URLs. But we need those so
# we don't have to have slashes at the end of URLs. So ignore.
@@ -152,7 +152,6 @@
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
- 'oxauth.backend.OpenStaxAccountsBackend',
)
TEMPLATES = [
@@ -203,6 +202,7 @@
'import_export',
'rangefilter',
'reversion',
+ 'wagtail_modeladmin',
# custom
'accounts',
'api',
@@ -239,7 +239,6 @@
'wagtail.sites',
'wagtail.api.v2',
'wagtail.contrib.settings',
- 'wagtail.contrib.modeladmin'
]
CRONJOBS = [
@@ -352,7 +351,7 @@
ALLOWED_HOSTS = json.loads(os.getenv('ALLOWED_HOSTS', '[]'))
-CNX_URL = os.getenv('CNX_URL')
+CNX_URL = os.getenv('CNX_URL', 'https://openstax.org')
# used in page.models to retrieve book information
CNX_ARCHIVE_URL = 'https://archive.cnx.org'
diff --git a/openstax/tests.py b/openstax/tests.py
index 3050dbafd..a0aeb08ba 100644
--- a/openstax/tests.py
+++ b/openstax/tests.py
@@ -134,7 +134,7 @@ def test_book_link_preview(self):
)
book_index.add_child(instance=book)
self.client = Client(HTTP_USER_AGENT='Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)')
- response = self.client.get('/details/books/biology-2e')
+ response = self.client.get('/details/books/biology-2e/')
self.assertContains(response, 'og:image')
def test_blog_link_preview(self):
@@ -185,6 +185,6 @@ def test_blog_link_preview(self):
))
self.news_index.add_child(instance=self.article)
self.client = Client(HTTP_USER_AGENT='facebookexternalhit/1.1')
- response = self.client.get('/blog/article-1')
+ response = self.client.get('/blog/article-1/')
self.assertContains(response, 'og:image')
diff --git a/openstax/urls.py b/openstax/urls.py
index d1a15114d..782c3aa61 100644
--- a/openstax/urls.py
+++ b/openstax/urls.py
@@ -2,13 +2,11 @@
from django.urls import include, path, re_path
from django.conf.urls.static import static
from django.contrib import admin
-from django.views.generic.base import RedirectView
from wagtail.admin import urls as wagtailadmin_urls
from wagtail import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls
from wagtail.images.views.serve import ServeView
from accounts import urls as accounts_urls
-from oxauth import views as oxauth_views
from .api import api_router
from news.search import search
@@ -17,14 +15,10 @@
from api import urls as api_urls
from global_settings.views import throw_error, clear_entire_cache
from wagtail.contrib.sitemaps.views import sitemap
-#from wagtailimportexport import urls as wagtailimportexport_urls
admin.site.site_header = 'OpenStax'
urlpatterns = [
- #path('admin/login/', oxauth_views.login),
- #path('admin/logout/', oxauth_views.logout),
- path('oxauth/', include('oxauth.urls')), # new auth package
path('admin/', include(wagtailadmin_urls)),
path('django-admin/error/', throw_error, name='throw_error'),
@@ -47,7 +41,6 @@
path('blog-feed/atom/', AtomBlogFeed()),
path('errata/', include('errata.urls')),
path('apps/cms/api/errata/', include('errata.urls')),
- #path('apps/cms/api/events/', include('events.urls')),
path('apps/cms/api/webinars/', include('webinars.urls')),
path('apps/cms/api/donations/', include('donations.urls')),
path('apps/cms/api/oxmenus/', include('oxmenus.urls')),
@@ -55,7 +48,6 @@
# route everything to /api/spike also...
path('apps/cms/api/spike/', include(wagtail_urls)),
path('sitemap.xml', sitemap),
- #path(r'', include(wagtailimportexport_urls)),
# For anything not caught by a more specific rule above, hand over to Wagtail's serving mechanism
path('', include(wagtail_urls)),
diff --git a/oxauth/admin.py b/oxauth/admin.py
deleted file mode 100644
index 8584e1b42..000000000
--- a/oxauth/admin.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from django.contrib import admin
-from .models import OpenStaxUserProfile
-
-class OpenStaxUserProfileAdmin(admin.ModelAdmin):
- list_display = ['user', 'openstax_accounts_uuid']
- search_fields = ['user', 'openstax_accounts_uuid' ]
- raw_id_fields = ['user']
-
- def has_add_permission(self, request):
- return False
-
-admin.site.register(OpenStaxUserProfile, OpenStaxUserProfileAdmin)
diff --git a/oxauth/backend.py b/oxauth/backend.py
deleted file mode 100644
index 67d694cd8..000000000
--- a/oxauth/backend.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from django.contrib.auth.backends import BaseBackend
-from django.conf import settings
-from .functions import decrypt_cookie
-from .models import OpenStaxUserProfile
-from django.contrib.auth.models import User
-
-class OpenStaxAccountsBackend(BaseBackend):
- def authenticate(self, request):
- decrypted_cookie = decrypt_cookie(request.COOKIES.get(settings.SSO_COOKIE_NAME)).payload_dict['sub']
-
- openstax_user = OpenStaxUserProfile.objects.get(openstax_accounts_uuid=decrypted_cookie['uuid'])
- return openstax_user.user
-
- def get_user(self, user_id):
- return User.objects.get(pk=user_id)
-
- def has_perm(self, user_obj, perm, obj=None):
- return user_obj.is_superuser == True
diff --git a/oxauth/functions.py b/oxauth/functions.py
index 4fe8cbd19..296ac539f 100644
--- a/oxauth/functions.py
+++ b/oxauth/functions.py
@@ -11,10 +11,10 @@
def decrypt_cookie(cookie):
strategy = Strategy2(
signature_public_key=settings.SIGNATURE_PUBLIC_KEY,
- signature_algorithm = settings.SIGNATURE_ALGORITHM,
- encryption_private_key = settings.ENCRYPTION_PRIVATE_KEY,
- encryption_method = 'A256GCM',
- encryption_algorithm = 'dir'
+ signature_algorithm=settings.SIGNATURE_ALGORITHM,
+ encryption_private_key=settings.ENCRYPTION_PRIVATE_KEY,
+ encryption_method='A256GCM',
+ encryption_algorithm='dir'
)
payload = strategy.decrypt(cookie)
@@ -125,4 +125,3 @@ def retrieve_user_data(url=None):
return user_data
else:
return {}
-
diff --git a/oxauth/models.py b/oxauth/models.py
index 8bd006edc..92c0f249d 100644
--- a/oxauth/models.py
+++ b/oxauth/models.py
@@ -1,6 +1,7 @@
from django.contrib.auth.models import User
from django.db import models
+# TODO: This can be removed, it's not being used but will do in another PR because it causing deployment issues.
class OpenStaxUserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
openstax_accounts_id = models.IntegerField()
@@ -8,6 +9,3 @@ class OpenStaxUserProfile(models.Model):
def __str__(self):
return self.user.username
-
- class Meta:
- verbose_name = 'OpenStax User Profile'
diff --git a/oxauth/templates/wagtailusers/users/edit.html b/oxauth/templates/wagtailusers/users/edit.html
index e66d553b5..72306b600 100644
--- a/oxauth/templates/wagtailusers/users/edit.html
+++ b/oxauth/templates/wagtailusers/users/edit.html
@@ -1,5 +1,7 @@
{% extends "wagtailusers/users/edit.html" %}
-{% block extra_fields %}
- {% include "wagtailadmin/shared/field_as_li.html" with field=form.is_staff %}
-{% endblock extra_fields %}
+
+ {% block extra_fields %}
+ {% include "wagtailadmin/shared/field.html" with field=form.is_staff %}
+ {% endblock extra_fields %}
+
Help us continue to make high-quality educational materials accessible by letting us know you've adopted! Our future grant funding is based on educator adoptions and the number of students we impact.
")
+ adoption_text = RichTextField(
+ default="
Help us continue to make high-quality educational materials accessible by letting us know you've adopted! Our future grant funding is based on educator adoptions and the number of students we impact.