diff --git a/www/conservancy/__init__.py b/www/conservancy/__init__.py index a80a32eac390c89fda5d5428739282fc6f3602d6..844bdf38c4497c12ffcf2e958e64c72652b9ee81 100644 --- a/www/conservancy/__init__.py +++ b/www/conservancy/__init__.py @@ -1,9 +1,12 @@ import hashlib from django.conf import settings -from django.shortcuts import render_to_response from django.template import RequestContext +# This is backwards compatibilty support for a custom function we wrote +# ourselves that is no longer necessary in modern Django. +from django.shortcuts import render as render_template_with_context + class ParameterValidator(object): def __init__(self, given_hash_or_params, params_hash_key=None): if params_hash_key is None: @@ -41,8 +44,3 @@ class ParameterValidator(object): def fail(self): self.valid = False - - -def render_template_with_context(request, template_path, context_dict): - return render_to_response(template_path, context_dict, - context_instance=RequestContext(request)) diff --git a/www/conservancy/apps/blog/models.py b/www/conservancy/apps/blog/models.py index 68b99caacecd029be9f2310b3f205b21b9a191e6..85dea531bbd62a0ccd1ef98b63b39e46a07db0ac 100644 --- a/www/conservancy/apps/blog/models.py +++ b/www/conservancy/apps/blog/models.py @@ -1,5 +1,6 @@ from django.db import models from django.conf import settings +from conservancy import bsoup from conservancy.apps.staff.models import Person from datetime import datetime, timedelta @@ -18,7 +19,7 @@ class EntryTag(models.Model): def get_absolute_url(self): return u"/blog/?tag=%s" % self.slug -class Entry(models.Model): +class Entry(models.Model, bsoup.SoupModelMixin): """Blog entry""" headline = models.CharField(max_length=200) @@ -38,6 +39,8 @@ class Entry(models.Model): ordering = ('-pub_date',) get_latest_by = 'pub_date' + SOUP_ATTRS = ['body'] + def __unicode__(self): return self.headline diff --git a/www/conservancy/apps/blog/urls.py b/www/conservancy/apps/blog/urls.py index d4d11cb0eb0981ccfb7656d5aa0e85de71846edd..4b084f7bc02eace5a3c0856ab98519d7db065dee 100644 --- a/www/conservancy/apps/blog/urls.py +++ b/www/conservancy/apps/blog/urls.py @@ -1,8 +1,8 @@ -from django.conf.urls import patterns, url, include +from django.conf.urls import url, include from conservancy.apps.blog.models import Entry, EntryTag # relative import from conservancy.apps.staff.models import Person from datetime import datetime -from conservancy.apps.blog.views import last_name, BlogYearArchiveView, BlogMonthArchiveView, BlogDayArchiveView, BlogDateDetailView +from conservancy.apps.blog.views import last_name, BlogYearArchiveView, BlogMonthArchiveView, BlogDayArchiveView, BlogDateDetailView, custom_index, query extra_context = {} @@ -12,23 +12,14 @@ info_dict = { 'extra_context': extra_context, } -# urlpatterns = patterns('django.views.generic.date_based', -urlpatterns = patterns('', - # (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/(?P[-\w]+)/$', 'object_detail', dict(info_dict, slug_field='slug')), - # (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/$', 'archive_day', info_dict), - # (r'^(?P\d{4})/(?P[a-z]{3})/$', 'archive_month', info_dict), - # (r'^(?P\d{4})/$', 'archive_year', dict(info_dict, - # make_object_list=True)), - (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/(?P[-\w]+)/$', BlogDateDetailView.as_view(**info_dict)), - (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/$', BlogDayArchiveView.as_view(**info_dict)), - (r'^(?P\d{4})/(?P[a-z]{3})/$', BlogMonthArchiveView.as_view(**info_dict)), - (r'^(?P\d{4})/$', BlogYearArchiveView.as_view(**info_dict)), -) - -urlpatterns += patterns('conservancy.apps.blog.views', - (r'^/?$', 'custom_index', dict(info_dict, paginate_by=4)), - (r'^query/$', 'query'), -) +urlpatterns = [ + url(r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/(?P[-\w]+)/$', BlogDateDetailView.as_view(**info_dict)), + url(r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/$', BlogDayArchiveView.as_view(**info_dict)), + url(r'^(?P\d{4})/(?P[a-z]{3})/$', BlogMonthArchiveView.as_view(**info_dict)), + url(r'^(?P\d{4})/$', BlogYearArchiveView.as_view(**info_dict)), + url(r'^/?$', custom_index, dict(info_dict, paginate_by=4)), + url(r'^query/$', query), +] # Code to display authors and tags on each blog page diff --git a/www/conservancy/apps/blog/views.py b/www/conservancy/apps/blog/views.py index 478f84741b29a0543988068d0b83215d7ccbb7be..86867de56c9c93a72bcef31edc46697335a07088 100644 --- a/www/conservancy/apps/blog/views.py +++ b/www/conservancy/apps/blog/views.py @@ -4,8 +4,7 @@ from django.views.generic import ListView from django.views.generic.dates import YearArchiveView, MonthArchiveView, DayArchiveView, DateDetailView from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from conservancy.apps.staff.models import Person -from django.shortcuts import get_object_or_404, render_to_response -from django.template import RequestContext +from django.shortcuts import get_object_or_404, render from datetime import datetime def OR_filter(field_name, objs): @@ -65,7 +64,7 @@ def custom_index(request, queryset, *args, **kwargs): extra_context['blog_entries'] = blog_entries - return render_to_response('blog/entry_list.html', extra_context, context_instance=RequestContext(request)) + return render(request, 'blog/entry_list.html', extra_context) def techblog_redirect(request): """Redirect from the old 'techblog' to the new blog @@ -103,8 +102,7 @@ def query(request): entry__isnull=False).distinct(), key=last_name) tags = EntryTag.objects.all().order_by('label') - return render_to_response('blog/query.html', - {'authors': authors, 'tags': tags}, context_instance=RequestContext(request)) + return render(request, 'blog/query.html', {'authors': authors, 'tags': tags}) def relative_redirect(request, path): from django import http diff --git a/www/conservancy/apps/contacts/views.py b/www/conservancy/apps/contacts/views.py index 53086d9c53b4846178ec90fd26ecb0b241ebb988..883377935596e94d7bfd933310f3ae47119e993d 100644 --- a/www/conservancy/apps/contacts/views.py +++ b/www/conservancy/apps/contacts/views.py @@ -1,5 +1,4 @@ -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.shortcuts import render from django import forms from conservancy.apps.contacts.models import ContactEntry from django.forms import ModelForm @@ -18,10 +17,8 @@ def subscribe(request): form = ContactEntryForm(request.POST) if form.is_valid(): form.save() - return render_to_response('contacts/subscribe_success.html', - {'form': form.cleaned_data}, context_instance=RequestContext(request)) + return render(request, 'contacts/subscribe_success.html', {'form': form.cleaned_data}) else: form = ContactEntryForm() - return render_to_response('contacts/subscribe.html', - {'form': form}, context_instance=RequestContext(request)) + return render(request, 'contacts/subscribe.html', {'form': form}) diff --git a/www/conservancy/apps/contractpatch/urls.py b/www/conservancy/apps/contractpatch/urls.py index 2d877ffaf363d6c19d65fa3979bc19c18ffcd5b0..6ab389f9230bef72202b4f38efb8ee0be3b3dfe5 100644 --- a/www/conservancy/apps/contractpatch/urls.py +++ b/www/conservancy/apps/contractpatch/urls.py @@ -1,6 +1,6 @@ -from django.conf.urls import patterns, url, include +from django.conf.urls import url, include +from conservancy.apps.contractpatch import views as cpatch_views -urlpatterns = patterns( - '', - (r'', 'conservancy.apps.contractpatch.views.index'), -) +urlpatterns = [ + url(r'', cpatch_views.index), +] diff --git a/www/conservancy/apps/events/views.py b/www/conservancy/apps/events/views.py index 5cbf6523cbc1ba966d7e6c10bd67869cd982e358..c01d283e4f9c74b779511cfc6845d6cee43403f4 100644 --- a/www/conservancy/apps/events/views.py +++ b/www/conservancy/apps/events/views.py @@ -1,6 +1,5 @@ # from django.views.generic.list_detail import object_list -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.shortcuts import render from django.http import Http404, HttpResponse from django.template import loader from django.core.exceptions import ObjectDoesNotExist @@ -21,7 +20,7 @@ def event_detail(request, year, slug, queryset, **kwargs): event = queryset.get(date__year=year, slug__exact=slug) except ObjectDoesNotExist: raise Http404, "Event does not exist" - return render_to_response('events/event_detail.html', {'event': event}, context_instance=RequestContext(request)) + return render(request, 'events/event_detail.html', {'event': event}) def custom_index(request, queryset, *args, **kwargs): """Scrollable index of future and past events, with date index. diff --git a/www/conservancy/apps/news/models.py b/www/conservancy/apps/news/models.py index 89e0cc4ce9cc19b052435feb4cf504e298cbec01..4fc5e3d93e5c8053fffc44afcbef0d6cc10aebfe 100644 --- a/www/conservancy/apps/news/models.py +++ b/www/conservancy/apps/news/models.py @@ -1,11 +1,12 @@ from django.db import models from django.conf import settings +from conservancy import bsoup from conservancy.apps.staff.models import Person from conservancy.apps.events.models import Event from django.contrib.sites.models import Site from datetime import datetime, timedelta -class PressRelease(models.Model): +class PressRelease(models.Model, bsoup.SoupModelMixin): """News release model""" headline = models.CharField(max_length=300) @@ -24,6 +25,8 @@ class PressRelease(models.Model): ordering = ("-pub_date",) get_latest_by = "pub_date" + SOUP_ATTRS = ['summary', 'body'] + def __unicode__(self): return self.headline diff --git a/www/conservancy/apps/news/templatetags/fill_url.py b/www/conservancy/apps/news/templatetags/fill_url.py new file mode 100644 index 0000000000000000000000000000000000000000..5d9d9a02c54ec65468284b231f1347cc97cb0b9c --- /dev/null +++ b/www/conservancy/apps/news/templatetags/fill_url.py @@ -0,0 +1,20 @@ +import urlparse + +from django import template + +register = template.Library() + +@register.filter(name='fill_url') +def fill_url(given_url, base_url): + """"Fill out" missing pieces of one URL from another. + + This function parses the given URL, and if it's missing any pieces + (scheme, netloc, etc.), it fills those in from the base URL. + Typical usage is "/URL/path"|fill_url:"https://hostname/" + to generate "https://hostname/URL/path". + """ + given_parts = urlparse.urlsplit(given_url) + base_parts = urlparse.urlsplit(base_url) + return urlparse.urlunsplit( + given_part or base_part for given_part, base_part in zip(given_parts, base_parts) + ) diff --git a/www/conservancy/apps/news/urls.py b/www/conservancy/apps/news/urls.py index 198c3488646c6f5128b6c91dfe2121fba990e7f9..1d621f12dddbf8c9133405912448a7d19033fd24 100644 --- a/www/conservancy/apps/news/urls.py +++ b/www/conservancy/apps/news/urls.py @@ -17,10 +17,10 @@ # along with this program in a file in the toplevel directory called # "AGPLv3". If not, see . -from django.conf.urls import patterns, url, include +from django.conf.urls import url, include from django.conf import settings from conservancy.apps.news.models import PressRelease, ExternalArticle -from conservancy.apps.news.views import NewsYearArchiveView, NewsMonthArchiveView, NewsDayArchiveView, NewsDateDetailView +from conservancy.apps.news.views import NewsYearArchiveView, NewsMonthArchiveView, NewsDayArchiveView, NewsDateDetailView, listing info_dict = { 'queryset': PressRelease.objects.all().filter(sites__id__exact=settings.SITE_ID), @@ -31,18 +31,10 @@ external_article_dict = { 'articles': ExternalArticle.objects.all() } -urlpatterns = patterns('', -# (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/(?P[-\w]+)/$', 'conservancy.apps.news.views.object_detail', info_dict), - (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/(?P[-\w]+)/$', NewsDateDetailView.as_view(**info_dict)), -# (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/$', 'conservancy.apps.news.views.archive_day', info_dict), - (r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/$', NewsDayArchiveView.as_view(**info_dict)), -# (r'^(?P\d{4})/(?P[a-z]{3})/$', 'conservancy.apps.news.views.archive_month', info_dict), - (r'^(?P\d{4})/(?P[a-z]{3})/$', NewsMonthArchiveView.as_view(**info_dict)), -# (r'^(?P\d{4})/$', 'conservancy.apps.news.views.archive_year', -# dict(info_dict, make_object_list=True)), - (r'^(?P\d{4})/$', NewsYearArchiveView.as_view(**info_dict)), -) - -urlpatterns += patterns('', - (r'^/?$', 'conservancy.apps.news.views.listing', dict(info_dict, paginate_by=6)), -) +urlpatterns = [ + url(r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/(?P[-\w]+)/$', NewsDateDetailView.as_view(**info_dict)), + url(r'^(?P\d{4})/(?P[a-z]{3})/(?P\w{1,2})/$', NewsDayArchiveView.as_view(**info_dict)), + url(r'^(?P\d{4})/(?P[a-z]{3})/$', NewsMonthArchiveView.as_view(**info_dict)), + url(r'^(?P\d{4})/$', NewsYearArchiveView.as_view(**info_dict)), + url(r'^/?$', listing, dict(info_dict, paginate_by=6)), +] diff --git a/www/conservancy/apps/news/views.py b/www/conservancy/apps/news/views.py index d38650affcc1d5b496ae072ec086acc48c171612..eb4fa79df72eb5a49b77a2e376b55c5328016b02 100644 --- a/www/conservancy/apps/news/views.py +++ b/www/conservancy/apps/news/views.py @@ -1,7 +1,6 @@ # from django.views.generic.list_detail import object_list from django.views.generic import ListView -from django.template import RequestContext -from django.shortcuts import render_to_response +from django.shortcuts import render from django.views.generic.dates import YearArchiveView, MonthArchiveView, DayArchiveView, DateDetailView from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from conservancy.apps.news.models import ExternalArticle @@ -42,7 +41,7 @@ def listing(request, *args, **kwargs): # If page is out of range (e.g. 9999), deliver last page of results. news = paginator.page(paginator.num_pages) - return render_to_response('news/pressrelease_list.html', {"news": news, "date_list" : date_list}, context_instance=RequestContext(request)) + return render(request, 'news/pressrelease_list.html', {"news": news, "date_list" : date_list}) class NewsYearArchiveView(YearArchiveView): # queryset = Article.objects.all() diff --git a/www/conservancy/apps/summit_registration/views.py b/www/conservancy/apps/summit_registration/views.py index fec386a4b25dbb124a68edd8097dc6ced46948c7..2dcf13951985a3dad79af8d0fb625b9c6d325b08 100644 --- a/www/conservancy/apps/summit_registration/views.py +++ b/www/conservancy/apps/summit_registration/views.py @@ -1,7 +1,5 @@ -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.shortcuts import render from django import forms -from django.template import RequestContext from conervancy.apps.summit_registration.models import SummitRegistration def register(request): @@ -21,10 +19,8 @@ def register(request): form = SummitForm(request.POST) if form.is_valid(): form.save() - return render_to_response('summit_registration/register_success.html', - {'form': form.cleaned_data}, context_instance=RequestContext(request)) + return render(reqeust, 'summit_registration/register_success.html', {'form': form.cleaned_data}) else: form = SummitForm() - return render_to_response('summit_registration/register.html', - {'form': form}, context_instance=RequestContext(request)) + return render(request, 'summit_registration/register.html', {'form': form}) diff --git a/www/conservancy/apps/supporter/urls.py b/www/conservancy/apps/supporter/urls.py index c5f9da3d9710ffeedd9a1e9641175a36b7e2dcae..a08853e21c3b8c51dbdb760cad942015f226c06e 100644 --- a/www/conservancy/apps/supporter/urls.py +++ b/www/conservancy/apps/supporter/urls.py @@ -1,11 +1,11 @@ -from django.conf.urls import patterns +from django.conf.urls import url +from conservancy.apps.supporter import views as supp_views +from conservancy.static import views as static_views -INDEX_VIEW = 'conservancy.apps.supporter.views.index' -pattern_pairs = [(r'^/?$', INDEX_VIEW)] -pattern_pairs.extend( - (r'^{}(?:\.html|/|)$'.format(basename), INDEX_VIEW) +INDEX_VIEW = supp_views.index +urlpatterns = [url(r'^/?$', INDEX_VIEW)] +urlpatterns.extend( + url(r'^{}(?:\.html|/|)$'.format(basename), INDEX_VIEW) for basename in ['index', '2015-supporter-appeal', '2016-supporter-appeal'] ) -pattern_pairs.append((r'', 'conservancy.static.views.index')) - -urlpatterns = patterns('', *pattern_pairs) +urlpatterns.append(url(r'', static_views.index)) diff --git a/www/conservancy/bsoup.py b/www/conservancy/bsoup.py new file mode 100644 index 0000000000000000000000000000000000000000..fb0ef6cb3d2ad50322ecc1a17f4212f713727a91 --- /dev/null +++ b/www/conservancy/bsoup.py @@ -0,0 +1,144 @@ +# -*- encoding: utf-8 -*- + +import io +import re + +import bs4 +import bs4.element + +class BeautifulSoup(bs4.BeautifulSoup): + """A wrapper of the original BeautifulSoup class, with convenience methods added.""" + + IMAGE_ATTRS = { + 'img': 'src', + 'video': 'poster', + } + NON_BODY_TEXT_TAGS = frozenset([ + 'img', + 'video', + ]) + SENTENCE_END = re.compile(r'[.?!]\s*\W*\s*$') + + def __init__(self, src, parser='html5lib'): + # WARNING! It seems like it would be ideal to use the 'lxml' parser + # for speed, but that doesn't work in our web application. On + # Debian stretch, at least, using lxml causes the web server WSGI + # application to go into an infinite loop. + super(BeautifulSoup, self).__init__(src, parser) + + def _body_text(self, root): + # "Body text" is all the strings under the root element, in order, + # except: + # * strings inside NON_BODY_TEXT_TAGS + # * strings inside containers of NON_BODY_TEXT_TAGS. A container is + # an element that has a NON_BODY_TEXT_TAGS element as its first child. + # For example, in
, none of the div's strings + # are included in the body text, because it's considered to be a + #