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 | ||||
| 
 | ||||
| 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)) | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'object_detail', dict(info_dict, slug_field='slug')), | ||||
|    # (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', info_dict), | ||||
|    # (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', info_dict), | ||||
|    # (r'^(?P<year>\d{4})/$', 'archive_year', dict(info_dict, | ||||
|    #                                              make_object_list=True)), | ||||
|    (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'), | ||||
| ) | ||||
| urlpatterns = [ | ||||
|     url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', BlogDateDetailView.as_view(**info_dict)), | ||||
|     url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', BlogDayArchiveView.as_view(**info_dict)), | ||||
|     url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', BlogMonthArchiveView.as_view(**info_dict)), | ||||
|     url(r'^(?P<year>\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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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}) | ||||
|  |  | |||
|  | @ -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), | ||||
| ] | ||||
|  |  | |||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										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 | ||||
| # "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 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<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$', 'conservancy.apps.news.views.object_detail', 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)), | ||||
| #   (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'conservancy.apps.news.views.archive_day', info_dict), | ||||
|    (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})/$', 'conservancy.apps.news.views.archive_month', info_dict), | ||||
|    (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)), | ||||
| ) | ||||
| urlpatterns = [ | ||||
|    url(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)), | ||||
|    url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', NewsMonthArchiveView.as_view(**info_dict)), | ||||
|    url(r'^(?P<year>\d{4})/$', NewsYearArchiveView.as_view(**info_dict)), | ||||
|    url(r'^/?$', listing, dict(info_dict, paginate_by=6)), | ||||
| ] | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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}) | ||||
|  |  | |||
|  | @ -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)) | ||||
|  |  | |||
							
								
								
									
										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 conservancy.apps.news.models import PressRelease | ||||
| from conservancy.apps.blog.models import Entry as BlogEntry | ||||
| from django.template import RequestContext | ||||
| from django.shortcuts import render_to_response | ||||
| from django.shortcuts import render | ||||
| from django.conf import settings | ||||
| from datetime import datetime | ||||
| 
 | ||||
|  | @ -255,4 +254,4 @@ def view(request): | |||
|     """ | ||||
| 
 | ||||
|     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 | ||||
| 
 | ||||
| def fundgoal_lookup(fundraiser_sought): | ||||
|  | @ -9,3 +10,11 @@ def fundgoal_lookup(fundraiser_sought): | |||
| 
 | ||||
| def sitefundraiser(request): | ||||
|     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' | ||||
| 
 | ||||
| ALLOWED_HOSTS = [ 'www.sfconservancy.org', 'aspen.sfconservancy.org', 'sfconservancy.org',  u'104.130.70.210' ] | ||||
| if DEBUG: | ||||
|     ALLOWED_HOSTS.append('localhost') | ||||
| 
 | ||||
| REDIRECT_TABLE = { | ||||
|     'www.sf-conservancy.org': 'sfconservancy.org', | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| from django.shortcuts import render_to_response | ||||
| from django.template import RequestContext | ||||
| from django.shortcuts import render | ||||
| from conservancy.apps.supporters.models import Supporter as Supporter | ||||
| from datetime import datetime, timedelta | ||||
| 
 | ||||
|  | @ -19,4 +18,4 @@ def view(request): | |||
|         'supporters_count' : supporters_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" %} | ||||
| 
 | ||||
| {% 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 content %} | ||||
|  |  | |||
|  | @ -1,5 +1,11 @@ | |||
| {% 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 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 | ||||
| # "AGPLv3".  If not, see <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| from django.conf.urls import patterns, url, include | ||||
| from django.contrib import admin | ||||
| from django.conf.urls import url, include | ||||
| from django.contrib import admin, admindocs | ||||
| 
 | ||||
| # import conservancy.settings | ||||
| from django.conf import settings | ||||
| from conservancy.feeds import BlogFeed, PressReleaseFeed, OmnibusFeed | ||||
| # 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' | ||||
| from conservancy import feeds, frontpage, sponsors | ||||
| import conservancy.apps.fundgoal.views as fundgoal_views | ||||
| import conservancy.static.views as static_views | ||||
| 
 | ||||
| admin.autodiscover() | ||||
| 
 | ||||
| urlpatterns = patterns('', | ||||
|     (r'^$', 'conservancy.frontpage.view'), | ||||
|     (r'^sponsors$', 'conservancy.frontpage.view'), | ||||
|     (r'^sponsors/$', 'conservancy.sponsors.view'), | ||||
|     (r'^sponsors/index.html$', 'conservancy.sponsors.view'), | ||||
|     (r'^admin/doc/', include('django.contrib.admindocs.urls')), | ||||
|     (r'^admin/', admin.site.urls), | ||||
|     (r'^feeds/blog/?$', BlogFeed()), | ||||
|     (r'^feeds/news/?$', PressReleaseFeed()), | ||||
|     (r'^feeds/omnibus/?$', OmnibusFeed()), | ||||
|     (r'^feeds/?$', 'conservancy.feeds.view'), | ||||
|     (r'^news(/|$)', include('conservancy.apps.news.urls')), | ||||
|     (r'^blog(/|$)', include('conservancy.apps.blog.urls')), | ||||
| urlpatterns = [ | ||||
|     url(r'^$', frontpage.view), | ||||
|     url(r'^sponsors$', frontpage.view), | ||||
|     url(r'^sponsors/$', sponsors.view), | ||||
|     url(r'^sponsors/index.html$', sponsors.view), | ||||
|     url(r'^admin/doc/', include('django.contrib.admindocs.urls')), | ||||
|     url(r'^admin/', admin.site.urls), | ||||
|     url(r'^feeds/blog/?$', feeds.BlogFeed()), | ||||
|     url(r'^feeds/news/?$', feeds.PressReleaseFeed()), | ||||
|     url(r'^feeds/omnibus/?$', feeds.OmnibusFeed()), | ||||
|     url(r'^feeds/?$', feeds.view), | ||||
|     url(r'^news(/|$)', include('conservancy.apps.news.urls')), | ||||
|     url(r'^blog(/|$)', include('conservancy.apps.blog.urls')), | ||||
|     # formerly static templated things... (dirs with templates) | ||||
|     (r'^error/(40[134]|500)(?:/index\.html|/|)$', 'conservancy.static.views.handler'), | ||||
|     (r'^error', 'conservancy.static.views.index'), | ||||
|     (r'^about', 'conservancy.static.views.index'), | ||||
|     (r'^donate', 'conservancy.static.views.index'), | ||||
|     (r'^copyleft-compliance', 'conservancy.static.views.index', | ||||
|     url(r'^error/(40[134]|500)(?:/index\.html|/|)$', static_views.handler), | ||||
|     url(r'^error', static_views.index), | ||||
|     url(r'^about', static_views.index), | ||||
|     url(r'^donate', static_views.index), | ||||
|     url(r'^copyleft-compliance', static_views.index, | ||||
|                            {'fundraiser_sought' : 'vmware-match-0'}), | ||||
|     (r'^projects', 'conservancy.static.views.index'), | ||||
|     (r'^npoacct', 'conservancy.static.views.index', | ||||
|     url(r'^projects', static_views.index), | ||||
|     url(r'^npoacct', static_views.index, | ||||
|                   {'fundraiser_sought' : 'npoacct'}), | ||||
|     (r'^contractpatch', include('conservancy.apps.contractpatch.urls')), | ||||
|     (r'^overview', 'conservancy.static.views.index'), | ||||
|     (r'^privacy-policy', 'conservancy.static.views.index'), | ||||
|     (r'^supporter', include('conservancy.apps.supporter.urls')), | ||||
|     (r'^fundraiser_data', 'conservancy.apps.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) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     url(r'^contractpatch', include('conservancy.apps.contractpatch.urls')), | ||||
|     url(r'^overview', static_views.index), | ||||
|     url(r'^privacy-policy', static_views.index), | ||||
|     url(r'^supporter', include('conservancy.apps.supporter.urls')), | ||||
|     url(r'^fundraiser_data', fundgoal_views.view), | ||||
| ] | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Bradley M. Kuhn
						Bradley M. Kuhn