Merge bkuhn master local branch with upstream
This commit is contained in:
		
						commit
						07faf5cebc
					
				
					 23 changed files with 343 additions and 152 deletions
				
			
		|  | @ -1,9 +1,12 @@ | ||||||
| import hashlib | import hashlib | ||||||
| 
 | 
 | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.shortcuts import render_to_response |  | ||||||
| from django.template import RequestContext | 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): | class ParameterValidator(object): | ||||||
|     def __init__(self, given_hash_or_params, params_hash_key=None): |     def __init__(self, given_hash_or_params, params_hash_key=None): | ||||||
|         if params_hash_key is None: |         if params_hash_key is None: | ||||||
|  | @ -41,8 +44,3 @@ class ParameterValidator(object): | ||||||
| 
 | 
 | ||||||
|     def fail(self): |     def fail(self): | ||||||
|         self.valid = False |         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)) |  | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
|  | from conservancy import bsoup | ||||||
| from conservancy.apps.staff.models import Person | from conservancy.apps.staff.models import Person | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| 
 | 
 | ||||||
|  | @ -18,7 +19,7 @@ class EntryTag(models.Model): | ||||||
|     def get_absolute_url(self): |     def get_absolute_url(self): | ||||||
|         return u"/blog/?tag=%s" % self.slug |         return u"/blog/?tag=%s" % self.slug | ||||||
| 
 | 
 | ||||||
| class Entry(models.Model): | class Entry(models.Model, bsoup.SoupModelMixin): | ||||||
|     """Blog entry""" |     """Blog entry""" | ||||||
| 
 | 
 | ||||||
|     headline = models.CharField(max_length=200) |     headline = models.CharField(max_length=200) | ||||||
|  | @ -38,6 +39,8 @@ class Entry(models.Model): | ||||||
|         ordering = ('-pub_date',) |         ordering = ('-pub_date',) | ||||||
|         get_latest_by = 'pub_date' |         get_latest_by = 'pub_date' | ||||||
| 
 | 
 | ||||||
|  |     SOUP_ATTRS = ['body'] | ||||||
|  | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.headline |         return self.headline | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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.blog.models import Entry, EntryTag # relative import | ||||||
| from conservancy.apps.staff.models import Person | from conservancy.apps.staff.models import Person | ||||||
| from datetime import datetime | 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 = {} | extra_context = {} | ||||||
| 
 | 
 | ||||||
|  | @ -12,23 +12,14 @@ info_dict = { | ||||||
|     'extra_context': extra_context, |     'extra_context': extra_context, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| # urlpatterns = patterns('django.views.generic.date_based', | urlpatterns = [ | ||||||
| urlpatterns = patterns('', |     url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', BlogDateDetailView.as_view(**info_dict)), | ||||||
|    # (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'object_detail', dict(info_dict, slug_field='slug')), |     url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', BlogDayArchiveView.as_view(**info_dict)), | ||||||
|    # (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', info_dict), |     url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', BlogMonthArchiveView.as_view(**info_dict)), | ||||||
|    # (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', info_dict), |     url(r'^(?P<year>\d{4})/$', BlogYearArchiveView.as_view(**info_dict)), | ||||||
|    # (r'^(?P<year>\d{4})/$', 'archive_year', dict(info_dict, |     url(r'^/?$', custom_index, dict(info_dict, paginate_by=4)), | ||||||
|    #                                              make_object_list=True)), |     url(r'^query/$', query), | ||||||
|    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', BlogDateDetailView.as_view(**info_dict)), | ] | ||||||
|    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', BlogDayArchiveView.as_view(**info_dict)), |  | ||||||
|    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', BlogMonthArchiveView.as_view(**info_dict)), |  | ||||||
|    (r'^(?P<year>\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'), |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| # Code to display authors and tags on each blog page | # Code to display authors and tags on each blog page | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,8 +4,7 @@ from django.views.generic import ListView | ||||||
| from django.views.generic.dates import YearArchiveView, MonthArchiveView, DayArchiveView, DateDetailView | from django.views.generic.dates import YearArchiveView, MonthArchiveView, DayArchiveView, DateDetailView | ||||||
| from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | ||||||
| from conservancy.apps.staff.models import Person | from conservancy.apps.staff.models import Person | ||||||
| from django.shortcuts import get_object_or_404, render_to_response | from django.shortcuts import get_object_or_404, render | ||||||
| from django.template import RequestContext |  | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| 
 | 
 | ||||||
| def OR_filter(field_name, objs): | def OR_filter(field_name, objs): | ||||||
|  | @ -65,7 +64,7 @@ def custom_index(request, queryset, *args, **kwargs): | ||||||
| 
 | 
 | ||||||
|     extra_context['blog_entries'] = blog_entries |     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): | def techblog_redirect(request): | ||||||
|     """Redirect from the old 'techblog' to the new blog |     """Redirect from the old 'techblog' to the new blog | ||||||
|  | @ -103,8 +102,7 @@ def query(request): | ||||||
|                                                entry__isnull=False).distinct(), |                                                entry__isnull=False).distinct(), | ||||||
|                          key=last_name) |                          key=last_name) | ||||||
|         tags = EntryTag.objects.all().order_by('label') |         tags = EntryTag.objects.all().order_by('label') | ||||||
|         return render_to_response('blog/query.html', |         return render(request, 'blog/query.html', {'authors': authors, 'tags': tags}) | ||||||
|                                   {'authors': authors, 'tags': tags}, context_instance=RequestContext(request)) |  | ||||||
| 
 | 
 | ||||||
| def relative_redirect(request, path): | def relative_redirect(request, path): | ||||||
|     from django import http |     from django import http | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| from django.shortcuts import render_to_response | from django.shortcuts import render | ||||||
| from django.template import RequestContext |  | ||||||
| from django import forms | from django import forms | ||||||
| from conservancy.apps.contacts.models import ContactEntry | from conservancy.apps.contacts.models import ContactEntry | ||||||
| from django.forms import ModelForm | from django.forms import ModelForm | ||||||
|  | @ -18,10 +17,8 @@ def subscribe(request): | ||||||
|         form = ContactEntryForm(request.POST) |         form = ContactEntryForm(request.POST) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|             form.save() |             form.save() | ||||||
|             return render_to_response('contacts/subscribe_success.html', |             return render(request, 'contacts/subscribe_success.html', {'form': form.cleaned_data}) | ||||||
|                                       {'form': form.cleaned_data}, context_instance=RequestContext(request)) |  | ||||||
|     else: |     else: | ||||||
|         form = ContactEntryForm() |         form = ContactEntryForm() | ||||||
| 
 | 
 | ||||||
|     return render_to_response('contacts/subscribe.html', |     return render(request, 'contacts/subscribe.html', {'form': form}) | ||||||
|                               {'form': form}, context_instance=RequestContext(request)) |  | ||||||
|  |  | ||||||
|  | @ -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( | urlpatterns = [ | ||||||
|     '', |     url(r'', cpatch_views.index), | ||||||
|     (r'', 'conservancy.apps.contractpatch.views.index'), | ] | ||||||
| ) |  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| # from django.views.generic.list_detail import object_list | # from django.views.generic.list_detail import object_list | ||||||
| from django.shortcuts import render_to_response | from django.shortcuts import render | ||||||
| from django.template import RequestContext |  | ||||||
| from django.http import Http404, HttpResponse | from django.http import Http404, HttpResponse | ||||||
| from django.template import loader | from django.template import loader | ||||||
| from django.core.exceptions import ObjectDoesNotExist | 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) |         event = queryset.get(date__year=year, slug__exact=slug) | ||||||
|     except ObjectDoesNotExist: |     except ObjectDoesNotExist: | ||||||
|         raise Http404, "Event does not exist" |         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): | def custom_index(request, queryset, *args, **kwargs): | ||||||
|     """Scrollable index of future and past events, with date index. |     """Scrollable index of future and past events, with date index. | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
|  | from conservancy import bsoup | ||||||
| from conservancy.apps.staff.models import Person | from conservancy.apps.staff.models import Person | ||||||
| from conservancy.apps.events.models import Event | from conservancy.apps.events.models import Event | ||||||
| from django.contrib.sites.models import Site | from django.contrib.sites.models import Site | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| 
 | 
 | ||||||
| class PressRelease(models.Model): | class PressRelease(models.Model, bsoup.SoupModelMixin): | ||||||
|     """News release model""" |     """News release model""" | ||||||
| 
 | 
 | ||||||
|     headline = models.CharField(max_length=300) |     headline = models.CharField(max_length=300) | ||||||
|  | @ -24,6 +25,8 @@ class PressRelease(models.Model): | ||||||
|         ordering = ("-pub_date",) |         ordering = ("-pub_date",) | ||||||
|         get_latest_by = "pub_date" |         get_latest_by = "pub_date" | ||||||
| 
 | 
 | ||||||
|  |     SOUP_ATTRS = ['summary', 'body'] | ||||||
|  | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.headline |         return self.headline | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								www/conservancy/apps/news/templatetags/fill_url.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								www/conservancy/apps/news/templatetags/fill_url.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||||
|  |     ) | ||||||
|  | @ -17,10 +17,10 @@ | ||||||
| # along with this program in a file in the toplevel directory called | # along with this program in a file in the toplevel directory called | ||||||
| # "AGPLv3".  If not, see <http://www.gnu.org/licenses/>. | # "AGPLv3".  If not, see <http://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| from django.conf.urls import patterns, url, include | from django.conf.urls import url, include | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from conservancy.apps.news.models import PressRelease, ExternalArticle | 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 = { | info_dict = { | ||||||
|     'queryset': PressRelease.objects.all().filter(sites__id__exact=settings.SITE_ID), |     'queryset': PressRelease.objects.all().filter(sites__id__exact=settings.SITE_ID), | ||||||
|  | @ -31,18 +31,10 @@ external_article_dict = { | ||||||
|     'articles': ExternalArticle.objects.all() |     'articles': ExternalArticle.objects.all() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('', | urlpatterns = [ | ||||||
| #    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'conservancy.apps.news.views.object_detail', info_dict), |    url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', NewsDateDetailView.as_view(**info_dict)), | ||||||
|    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', NewsDateDetailView.as_view(**info_dict)), |    url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', NewsDayArchiveView.as_view(**info_dict)), | ||||||
| #   (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'conservancy.apps.news.views.archive_day', info_dict), |    url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', NewsMonthArchiveView.as_view(**info_dict)), | ||||||
|    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', NewsDayArchiveView.as_view(**info_dict)), |    url(r'^(?P<year>\d{4})/$', NewsYearArchiveView.as_view(**info_dict)), | ||||||
| #   (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'conservancy.apps.news.views.archive_month', info_dict), |    url(r'^/?$', listing, dict(info_dict, paginate_by=6)), | ||||||
|    (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', NewsMonthArchiveView.as_view(**info_dict)), | ] | ||||||
| #   (r'^(?P<year>\d{4})/$', 'conservancy.apps.news.views.archive_year', |  | ||||||
| #    dict(info_dict, make_object_list=True)), |  | ||||||
|    (r'^(?P<year>\d{4})/$', NewsYearArchiveView.as_view(**info_dict)), |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| urlpatterns += patterns('', |  | ||||||
|    (r'^/?$', 'conservancy.apps.news.views.listing', dict(info_dict, paginate_by=6)), |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| # from django.views.generic.list_detail import object_list | # from django.views.generic.list_detail import object_list | ||||||
| from django.views.generic import ListView | from django.views.generic import ListView | ||||||
| from django.template import RequestContext | from django.shortcuts import render | ||||||
| from django.shortcuts import render_to_response |  | ||||||
| from django.views.generic.dates import YearArchiveView, MonthArchiveView, DayArchiveView, DateDetailView | from django.views.generic.dates import YearArchiveView, MonthArchiveView, DayArchiveView, DateDetailView | ||||||
| from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | ||||||
| from conservancy.apps.news.models import ExternalArticle | 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. |         # If page is out of range (e.g. 9999), deliver last page of results. | ||||||
|         news = paginator.page(paginator.num_pages) |         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): | class NewsYearArchiveView(YearArchiveView): | ||||||
|     # queryset = Article.objects.all() |     # queryset = Article.objects.all() | ||||||
|  |  | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| from django.shortcuts import render_to_response | from django.shortcuts import render | ||||||
| from django.template import RequestContext |  | ||||||
| from django import forms | from django import forms | ||||||
| from django.template import RequestContext |  | ||||||
| from conervancy.apps.summit_registration.models import SummitRegistration | from conervancy.apps.summit_registration.models import SummitRegistration | ||||||
| 
 | 
 | ||||||
| def register(request): | def register(request): | ||||||
|  | @ -21,10 +19,8 @@ def register(request): | ||||||
|         form = SummitForm(request.POST) |         form = SummitForm(request.POST) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|             form.save() |             form.save() | ||||||
|             return render_to_response('summit_registration/register_success.html', |             return render(reqeust, 'summit_registration/register_success.html', {'form': form.cleaned_data}) | ||||||
|                                       {'form': form.cleaned_data}, context_instance=RequestContext(request)) |  | ||||||
|     else: |     else: | ||||||
|         form = SummitForm() |         form = SummitForm() | ||||||
| 
 | 
 | ||||||
|     return render_to_response('summit_registration/register.html', |     return render(request, 'summit_registration/register.html', {'form': form}) | ||||||
|                               {'form': form}, context_instance=RequestContext(request)) |  | ||||||
|  |  | ||||||
|  | @ -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' | INDEX_VIEW = supp_views.index | ||||||
| pattern_pairs = [(r'^/?$', INDEX_VIEW)] | urlpatterns = [url(r'^/?$', INDEX_VIEW)] | ||||||
| pattern_pairs.extend( | urlpatterns.extend( | ||||||
|     (r'^{}(?:\.html|/|)$'.format(basename), INDEX_VIEW) |     url(r'^{}(?:\.html|/|)$'.format(basename), INDEX_VIEW) | ||||||
|     for basename in ['index', '2015-supporter-appeal', '2016-supporter-appeal'] |     for basename in ['index', '2015-supporter-appeal', '2016-supporter-appeal'] | ||||||
| ) | ) | ||||||
| pattern_pairs.append((r'', 'conservancy.static.views.index')) | urlpatterns.append(url(r'', static_views.index)) | ||||||
| 
 |  | ||||||
| urlpatterns = patterns('', *pattern_pairs) |  | ||||||
|  |  | ||||||
							
								
								
									
										144
									
								
								www/conservancy/bsoup.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								www/conservancy/bsoup.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <div> <video …> … </div>, none of the div's strings | ||||||
|  |         #   are included in the body text, because it's considered to be a | ||||||
|  |         #   <video> container, and any strings are probably a caption, fallback | ||||||
|  |         #   text, or other non-body text. | ||||||
|  |         started = False | ||||||
|  |         for child in root.children: | ||||||
|  |             child_type = type(child) | ||||||
|  |             if issubclass(child_type, bs4.element.Tag): | ||||||
|  |                 if child.name in self.NON_BODY_TEXT_TAGS: | ||||||
|  |                     if not started: | ||||||
|  |                         break | ||||||
|  |                 else: | ||||||
|  |                     for s in self._body_text(child): | ||||||
|  |                         yield s | ||||||
|  |             # It's not worth it to use issubclass here, because elements that | ||||||
|  |             # don't have body text like Comments and CDATA are subclasses of | ||||||
|  |             # NavigableString. | ||||||
|  |             elif child_type is bs4.element.NavigableString: | ||||||
|  |                 if started: | ||||||
|  |                     yield child | ||||||
|  |                 elif child.isspace(): | ||||||
|  |                     pass | ||||||
|  |                 else: | ||||||
|  |                     yield child | ||||||
|  |                     started = True | ||||||
|  | 
 | ||||||
|  |     def body_text(self): | ||||||
|  |         """Return an iterator of strings comprising this document's body text.""" | ||||||
|  |         return self._body_text(self) | ||||||
|  | 
 | ||||||
|  |     def some_body_text(self, char_target=300): | ||||||
|  |         """Return an iterator of strings with some of this document's body text. | ||||||
|  | 
 | ||||||
|  |         This is the same as body_text, except after it yields a string that | ||||||
|  |         looks like the end of a sentence, it checks whether it has yielded | ||||||
|  |         at least `char_target` characters.  If so, the iterator stops. | ||||||
|  |         """ | ||||||
|  |         # This implementation is likely to overshoot `char_target` a lot, | ||||||
|  |         # because it doesn't look inside the strings it yields, just at the | ||||||
|  |         # end of them.  We can implement something smarter later if needed. | ||||||
|  |         char_count = 0 | ||||||
|  |         for s in self.body_text(): | ||||||
|  |             yield s | ||||||
|  |             char_count += len(s) | ||||||
|  |             if (char_count > char_target) and self.SENTENCE_END.search(s): | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def is_video_source(elem): | ||||||
|  |         try: | ||||||
|  |             return elem.name == 'source' and elem.parent.name == 'video' | ||||||
|  |         except AttributeError: | ||||||
|  |             return False | ||||||
|  | 
 | ||||||
|  |     def iter_attr(self, tag, attr_name, **kwargs): | ||||||
|  |         kwargs[attr_name] = True | ||||||
|  |         for elem in self.find_all(tag, **kwargs): | ||||||
|  |             yield elem[attr_name] | ||||||
|  | 
 | ||||||
|  |     def iter_image_urls(self): | ||||||
|  |         """Return an iterator of source URL strings of all images in this document. | ||||||
|  | 
 | ||||||
|  |         Images include <img> tags and <video> poster attributes. | ||||||
|  |         """ | ||||||
|  |         for elem in self.find_all(list(self.IMAGE_ATTRS.keys())): | ||||||
|  |             try: | ||||||
|  |                 yield elem[self.IMAGE_ATTRS[elem.name]] | ||||||
|  |             except KeyError: | ||||||
|  |                 pass | ||||||
|  | 
 | ||||||
|  |     def iter_video_urls(self): | ||||||
|  |         """Return an iterator of source URL strings of all videos in this document.""" | ||||||
|  |         return self.iter_attr(self.is_video_source, 'src') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoupModelMixin: | ||||||
|  |     """Mixin for models to parse HTML with BeautifulSoup. | ||||||
|  | 
 | ||||||
|  |     Classes that use this mixin must define `SOUP_ATTRS`, a list of strings | ||||||
|  |     that name attributes with HTML in them.  After that, all the public methods | ||||||
|  |     are usable. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     SOUP_ATTRS = [] | ||||||
|  | 
 | ||||||
|  |     def _get_soup(self): | ||||||
|  |         try: | ||||||
|  |             return self._soup | ||||||
|  |         except AttributeError: | ||||||
|  |             html = io.StringIO() | ||||||
|  |             for attr_name in self.SOUP_ATTRS: | ||||||
|  |                 html.write(getattr(self, attr_name)) | ||||||
|  |             html.seek(0) | ||||||
|  |             self._soup = BeautifulSoup(html) | ||||||
|  |             return self._soup | ||||||
|  | 
 | ||||||
|  |     def get_description(self): | ||||||
|  |         """Return a string with a brief excerpt of body text from the HTML.""" | ||||||
|  |         return u''.join(self._get_soup().some_body_text()) | ||||||
|  | 
 | ||||||
|  |     def get_image_urls(self): | ||||||
|  |         """Return an iterator of source URL strings of all images in the HTML. | ||||||
|  | 
 | ||||||
|  |         Images include <img> tags and <video> poster attributes. | ||||||
|  |         """ | ||||||
|  |         return self._get_soup().iter_image_urls() | ||||||
|  | 
 | ||||||
|  |     def get_video_urls(self): | ||||||
|  |         """Return an iterator of source URL strings of all videos in the HTML.""" | ||||||
|  |         return self._get_soup().iter_video_urls() | ||||||
|  | @ -2,8 +2,7 @@ from django.contrib.syndication.views import Feed | ||||||
| from django.utils.feedgenerator import Rss201rev2Feed  | from django.utils.feedgenerator import Rss201rev2Feed  | ||||||
| from conservancy.apps.news.models import PressRelease | from conservancy.apps.news.models import PressRelease | ||||||
| from conservancy.apps.blog.models import Entry as BlogEntry | from conservancy.apps.blog.models import Entry as BlogEntry | ||||||
| from django.template import RequestContext | from django.shortcuts import render | ||||||
| from django.shortcuts import render_to_response |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| 
 | 
 | ||||||
|  | @ -255,4 +254,4 @@ def view(request): | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     feeds = (PressReleaseFeed, BlogFeed, OmnibusFeed) |     feeds = (PressReleaseFeed, BlogFeed, OmnibusFeed) | ||||||
|     return render_to_response("feeds.html", {'feeds': feeds}, context_instance=RequestContext(request)) |     return render(request, "feeds.html", {'feeds': feeds}) | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | import conservancy.settings | ||||||
| from conservancy.apps.fundgoal.models import FundraisingGoal as FundraisingGoal | from conservancy.apps.fundgoal.models import FundraisingGoal as FundraisingGoal | ||||||
| 
 | 
 | ||||||
| def fundgoal_lookup(fundraiser_sought): | def fundgoal_lookup(fundraiser_sought): | ||||||
|  | @ -9,3 +10,11 @@ def fundgoal_lookup(fundraiser_sought): | ||||||
| 
 | 
 | ||||||
| def sitefundraiser(request): | def sitefundraiser(request): | ||||||
|     return {'sitefundgoal': fundgoal_lookup('supporterrun') } |     return {'sitefundgoal': fundgoal_lookup('supporterrun') } | ||||||
|  | 
 | ||||||
|  | if conservancy.settings.FORCE_CANONICAL_HOSTNAME: | ||||||
|  |     _HOST_URL_VAR = {'host_url': 'https://' + conservancy.settings.FORCE_CANONICAL_HOSTNAME} | ||||||
|  |     def host_url(request): | ||||||
|  |         return _HOST_URL_VAR | ||||||
|  | else: | ||||||
|  |     def host_url(request): | ||||||
|  |         return {'host_url': request.build_absolute_uri('/').rstrip('/')} | ||||||
|  |  | ||||||
|  | @ -27,6 +27,8 @@ ROOT_URLCONF = 'conservancy.urls' | ||||||
| FORCE_CANONICAL_HOSTNAME = False if DEBUG else 'sfconservancy.org' | FORCE_CANONICAL_HOSTNAME = False if DEBUG else 'sfconservancy.org' | ||||||
| 
 | 
 | ||||||
| ALLOWED_HOSTS = [ 'www.sfconservancy.org', 'aspen.sfconservancy.org', 'sfconservancy.org',  u'104.130.70.210' ] | ALLOWED_HOSTS = [ 'www.sfconservancy.org', 'aspen.sfconservancy.org', 'sfconservancy.org',  u'104.130.70.210' ] | ||||||
|  | if DEBUG: | ||||||
|  |     ALLOWED_HOSTS.append('localhost') | ||||||
| 
 | 
 | ||||||
| REDIRECT_TABLE = { | REDIRECT_TABLE = { | ||||||
|     'www.sf-conservancy.org': 'sfconservancy.org', |     'www.sf-conservancy.org': 'sfconservancy.org', | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| from django.shortcuts import render_to_response | from django.shortcuts import render | ||||||
| from django.template import RequestContext |  | ||||||
| from conservancy.apps.supporters.models import Supporter as Supporter | from conservancy.apps.supporters.models import Supporter as Supporter | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| 
 | 
 | ||||||
|  | @ -19,4 +18,4 @@ def view(request): | ||||||
|         'supporters_count' : supporters_count, |         'supporters_count' : supporters_count, | ||||||
|         'anonymous_count' : anonymous_count |         'anonymous_count' : anonymous_count | ||||||
|     } |     } | ||||||
|     return render_to_response("sponsors.html", c, context_instance=RequestContext(request)) |     return render(request, "sponsors.html", c) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,11 @@ | ||||||
| {% extends "base_blog.html" %} | {% extends "base_blog.html" %} | ||||||
| 
 | 
 | ||||||
|  | {% block head %} | ||||||
|  | {% include "opengraph_partial.html" with url=object.get_absolute_url title=object.headline description=object.get_description %} | ||||||
|  | {% include "opengraph_urllist_partial.html" with property='image' urls=object.get_image_urls fallback='/img/conservancy-logo.png' %} | ||||||
|  | {% include "opengraph_urllist_partial.html" with property='video' urls=object.get_video_urls %} | ||||||
|  | {% endblock %} | ||||||
|  | 
 | ||||||
| {% block subtitle %}{{ object.headline|striptags|safe }} - Conservancy Blog - {% endblock %} | {% block subtitle %}{{ object.headline|striptags|safe }} - Conservancy Blog - {% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
|  |  | ||||||
|  | @ -1,5 +1,11 @@ | ||||||
| {% extends "base_news.html" %} | {% extends "base_news.html" %} | ||||||
| 
 | 
 | ||||||
|  | {% block head %} | ||||||
|  | {% include "opengraph_partial.html" with url=object.get_absolute_url title=object.headline description=object.get_description %} | ||||||
|  | {% include "opengraph_urllist_partial.html" with property='image' urls=object.get_image_urls fallback='/img/conservancy-logo.png' %} | ||||||
|  | {% include "opengraph_urllist_partial.html" with property='video' urls=object.get_video_urls %} | ||||||
|  | {% endblock %} | ||||||
|  | 
 | ||||||
| {% block subtitle %}{{ object.headline|striptags|safe }} - {% endblock %} | {% block subtitle %}{{ object.headline|striptags|safe }} - {% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
|  |  | ||||||
							
								
								
									
										38
									
								
								www/conservancy/templates/opengraph_partial.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								www/conservancy/templates/opengraph_partial.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | {% comment %} | ||||||
|  | 
 | ||||||
|  | Include this partial in a head section to include basic Open Graph metadata. | ||||||
|  | Pass a variable `NAME` to give a value for the `og:NAME` property. | ||||||
|  | 
 | ||||||
|  | These properties are only listed if you give a value for them: | ||||||
|  | 
 | ||||||
|  | * url: A URL string that includes at least an absolute path.  This partial | ||||||
|  |   will fill in a default scheme and host if needed. | ||||||
|  | * title: A string.  Tags are stripped, then the rest is assumed HTML-safe. | ||||||
|  | * description: A string.  Tags are stripped, then the rest is assumed | ||||||
|  |   HTML-safe. | ||||||
|  | 
 | ||||||
|  | These properties are always included.  You can override them but you | ||||||
|  | normally shouldn't need to: | ||||||
|  | 
 | ||||||
|  | * type: Default "website". | ||||||
|  | * locale: Default "en_US". | ||||||
|  | * site_name: Default "Software Freedom Conservancy" | ||||||
|  | 
 | ||||||
|  | {% endcomment %} | ||||||
|  | 
 | ||||||
|  | <meta property="og:type" content="{{ type|default:"website" }}"> | ||||||
|  | <meta property="og:locale" content="{{ locale|default:"en_US" }}"> | ||||||
|  | <meta property="og:site_name" content="{{ site_name|default:"Software Freedom Conservancy" }}"> | ||||||
|  | 
 | ||||||
|  | {% if url %} | ||||||
|  | {% load fill_url %} | ||||||
|  | <meta property="og:url" content="{{ url|fill_url:host_url }}"> | ||||||
|  | {% endif %} | ||||||
|  | 
 | ||||||
|  | {% if title %} | ||||||
|  | <meta property="og:title" content="{{ title|striptags|safe }}"> | ||||||
|  | {% endif %} | ||||||
|  | 
 | ||||||
|  | {% if description %} | ||||||
|  | <meta property="og:description" content="{{ description|striptags|safe }}"> | ||||||
|  | {% endif %} | ||||||
							
								
								
									
										26
									
								
								www/conservancy/templates/opengraph_urllist_partial.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								www/conservancy/templates/opengraph_urllist_partial.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | {% comment %} | ||||||
|  | 
 | ||||||
|  | Include this partial in a head section to include a series of URLs for a | ||||||
|  | given property, like og:image or og:video. | ||||||
|  | 
 | ||||||
|  | You must pass the following variables: | ||||||
|  | 
 | ||||||
|  | * property: A string with the name of the property, like 'image' or 'video'. | ||||||
|  | * urls: A sequence of URL strings.  Each should include at least an absolute | ||||||
|  |   path.  This partial will fill in a scheme and host if needed. | ||||||
|  | 
 | ||||||
|  | You may also pass: | ||||||
|  | 
 | ||||||
|  | * fallback: A URL string, following the same rules as in `urls`.  This URL | ||||||
|  |   will be used if `urls` is empty. | ||||||
|  | 
 | ||||||
|  | {% endcomment %} | ||||||
|  | 
 | ||||||
|  | {% load fill_url %} | ||||||
|  | {% for url in urls %} | ||||||
|  | <meta property="og:{{ property }}" content="{{ url|fill_url:host_url }}"> | ||||||
|  | {% empty %} | ||||||
|  | {% if fallback %} | ||||||
|  | <meta property="og:{{ property }}" content="{{ fallback|fill_url:host_url }}"> | ||||||
|  | {% endif %} | ||||||
|  | {% endfor %} | ||||||
|  | @ -17,75 +17,41 @@ | ||||||
| # along with this program in a file in the toplevel directory called | # along with this program in a file in the toplevel directory called | ||||||
| # "AGPLv3".  If not, see <http://www.gnu.org/licenses/>. | # "AGPLv3".  If not, see <http://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| from django.conf.urls import patterns, url, include | from django.conf.urls import url, include | ||||||
| from django.contrib import admin | from django.contrib import admin, admindocs | ||||||
| 
 | 
 | ||||||
| # import conservancy.settings | from conservancy import feeds, frontpage, sponsors | ||||||
| from django.conf import settings | import conservancy.apps.fundgoal.views as fundgoal_views | ||||||
| from conservancy.feeds import BlogFeed, PressReleaseFeed, OmnibusFeed | import conservancy.static.views as static_views | ||||||
| # from django.views.static import serve |  | ||||||
| # from django.conf.urls.static import static |  | ||||||
| # from django.contrib.staticfiles.urls import staticfiles_urlpatterns |  | ||||||
| # import conservancy.static.overview.views |  | ||||||
| 
 |  | ||||||
| # handler404 = 'modpythoncustom.view404' |  | ||||||
| # handler401 = 'conservancy.static.views.handler401' |  | ||||||
| # handler403 = 'conservancy.static.views.handler403' |  | ||||||
| handler404 = 'conservancy.static.views.handler404' |  | ||||||
| # handler500 = 'conservancy.static.views.handler500' |  | ||||||
| 
 | 
 | ||||||
| admin.autodiscover() | admin.autodiscover() | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('', | urlpatterns = [ | ||||||
|     (r'^$', 'conservancy.frontpage.view'), |     url(r'^$', frontpage.view), | ||||||
|     (r'^sponsors$', 'conservancy.frontpage.view'), |     url(r'^sponsors$', frontpage.view), | ||||||
|     (r'^sponsors/$', 'conservancy.sponsors.view'), |     url(r'^sponsors/$', sponsors.view), | ||||||
|     (r'^sponsors/index.html$', 'conservancy.sponsors.view'), |     url(r'^sponsors/index.html$', sponsors.view), | ||||||
|     (r'^admin/doc/', include('django.contrib.admindocs.urls')), |     url(r'^admin/doc/', include('django.contrib.admindocs.urls')), | ||||||
|     (r'^admin/', admin.site.urls), |     url(r'^admin/', admin.site.urls), | ||||||
|     (r'^feeds/blog/?$', BlogFeed()), |     url(r'^feeds/blog/?$', feeds.BlogFeed()), | ||||||
|     (r'^feeds/news/?$', PressReleaseFeed()), |     url(r'^feeds/news/?$', feeds.PressReleaseFeed()), | ||||||
|     (r'^feeds/omnibus/?$', OmnibusFeed()), |     url(r'^feeds/omnibus/?$', feeds.OmnibusFeed()), | ||||||
|     (r'^feeds/?$', 'conservancy.feeds.view'), |     url(r'^feeds/?$', feeds.view), | ||||||
|     (r'^news(/|$)', include('conservancy.apps.news.urls')), |     url(r'^news(/|$)', include('conservancy.apps.news.urls')), | ||||||
|     (r'^blog(/|$)', include('conservancy.apps.blog.urls')), |     url(r'^blog(/|$)', include('conservancy.apps.blog.urls')), | ||||||
|     # formerly static templated things... (dirs with templates) |     # formerly static templated things... (dirs with templates) | ||||||
|     (r'^error/(40[134]|500)(?:/index\.html|/|)$', 'conservancy.static.views.handler'), |     url(r'^error/(40[134]|500)(?:/index\.html|/|)$', static_views.handler), | ||||||
|     (r'^error', 'conservancy.static.views.index'), |     url(r'^error', static_views.index), | ||||||
|     (r'^about', 'conservancy.static.views.index'), |     url(r'^about', static_views.index), | ||||||
|     (r'^donate', 'conservancy.static.views.index'), |     url(r'^donate', static_views.index), | ||||||
|     (r'^copyleft-compliance', 'conservancy.static.views.index', |     url(r'^copyleft-compliance', static_views.index, | ||||||
|                            {'fundraiser_sought' : 'vmware-match-0'}), |                            {'fundraiser_sought' : 'vmware-match-0'}), | ||||||
|     (r'^projects', 'conservancy.static.views.index'), |     url(r'^projects', static_views.index), | ||||||
|     (r'^npoacct', 'conservancy.static.views.index', |     url(r'^npoacct', static_views.index, | ||||||
|                   {'fundraiser_sought' : 'npoacct'}), |                   {'fundraiser_sought' : 'npoacct'}), | ||||||
|     (r'^contractpatch', include('conservancy.apps.contractpatch.urls')), |     url(r'^contractpatch', include('conservancy.apps.contractpatch.urls')), | ||||||
|     (r'^overview', 'conservancy.static.views.index'), |     url(r'^overview', static_views.index), | ||||||
|     (r'^privacy-policy', 'conservancy.static.views.index'), |     url(r'^privacy-policy', static_views.index), | ||||||
|     (r'^supporter', include('conservancy.apps.supporter.urls')), |     url(r'^supporter', include('conservancy.apps.supporter.urls')), | ||||||
|     (r'^fundraiser_data', 'conservancy.apps.fundgoal.views.view'), |     url(r'^fundraiser_data', fundgoal_views.view), | ||||||
| ) | ] | ||||||
| 
 |  | ||||||
| # urlpatterns += url(regex  = r'^%s(?P<path>.*)$' % conservancy.settings.STATIC_URL[1:], |  | ||||||
| # urlpatterns += url(regex  = r'^/overview', |  | ||||||
| #                    view   = 'django.views.static.serve', |  | ||||||
| #                    kwargs = {'document_root': conservancy.settings.STATIC_ROOT, |  | ||||||
| #                              'show_indexes' : True}) |  | ||||||
| # urlpatterns += (r'^(?P<path>.*)$', 'django.views.static.serve',  |  | ||||||
| # urlpatterns += (r'^overview/$', 'django.views.static.serve',  |  | ||||||
| #                 {'document_root': conservancy.settings.STATIC_ROOT, |  | ||||||
| #                  'show_indexes' : True}) |  | ||||||
| 
 |  | ||||||
| # https://docs.djangoproject.com/en/1.7/howto/static-files/ |  | ||||||
| #  + static(conservancy.settings.STATIC_URL, document_root=conservancy.settings.STATIC_ROOT) |  | ||||||
| 
 |  | ||||||
| # urlpatterns += staticfiles_urlpatterns() |  | ||||||
| 
 |  | ||||||
| # urlpatterns += static(settings.STATIC_URL, view='django.contrib.staticfiles.views.serve', |  | ||||||
| # urlpatterns += static('/', view='django.contrib.staticfiles.views.serve', |  | ||||||
| #                       document_root=settings.STATIC_ROOT, |  | ||||||
| #                       show_indexes=True) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Bradley M. Kuhn
						Bradley M. Kuhn