diff options
-rw-r--r-- | app/books/urls.py | 2 | ||||
-rw-r--r-- | app/builder/base.py | 118 | ||||
-rw-r--r-- | app/jrnl/build.py | 48 | ||||
-rw-r--r-- | app/jrnl/models.py | 2 | ||||
-rw-r--r-- | app/jrnl/urls.py | 16 | ||||
-rw-r--r-- | app/jrnl/views.py | 33 | ||||
-rw-r--r-- | app/photos/urls.py | 4 | ||||
-rw-r--r-- | design/templates/archives/writing_date.html | 2 | ||||
-rw-r--r-- | design/templates/details/entry.amp | 175 |
9 files changed, 384 insertions, 16 deletions
diff --git a/app/books/urls.py b/app/books/urls.py index 003d6d6..3efd730 100644 --- a/app/books/urls.py +++ b/app/books/urls.py @@ -5,5 +5,5 @@ urlpatterns = patterns('', #url(r'(?P<slug>[-\w]+)/(?P<page>\d+)/$', 'blog.views.entry_list_by_area'), url(r'(?P<page>\d+)/$', 'books.views.book_list'), url(r'(?P<slug>[-\w]+)/$', 'books.views.book_detail'), - url(r'^$', RedirectView.as_view(url="/books/1/")), + url(r'^$', RedirectView.as_view(url="/books/1/", permanent=False)), ) diff --git a/app/builder/base.py b/app/builder/base.py index 62cfad0..ded8bba 100644 --- a/app/builder/base.py +++ b/app/builder/base.py @@ -4,12 +4,128 @@ from decimal import Decimal from django.test.client import Client from django.template.loader import render_to_string from django.template import Context +from django.core.urlresolvers import reverse from django.apps import apps from django.conf import settings +from jsmin import jsmin + + +class _FileWriter(object): + """ + Given a path and text object; write the page to disc + """ + def __init__(self, path, text_object, ext='html', filename='index', base_path=settings.NFLATFILES_ROOT): + self.path = '%s%s' % (base_path, path) + if not os.path.isdir(self.path): + os.makedirs(self.path) + fpath = '%s%s.%s' % (self.path, filename, ext) + self.write(fpath, text_object) + if ext == 'js': + self.compress_js(filename, text_object) + + def write(self, fpath, text_object): + f = open(fpath, 'wb') + f.write(text_object) + f.close() + + def compress_js(self, filename, text_object): + path = '%s%s.min.js' % (self.path, filename) + compressed = jsmin(text_object.decode(encoding='UTF-8')) + self.write(path, compressed) + + +class BuildNew(): + def __init__(self, model, app): + self.model = apps.get_model(model, app) + self.get_model_queryset() + self.client = Client() + + def build(self): + self.build_list_view() + self.build_detail_view() + + def get_model_queryset(self): + return self.model.objects.filter(status__exact=1) + + def write_file(self, path, text_object, ext='html', filename='index'): + self.writer = _FileWriter(path, text_object, ext=ext, filename=filename) + + def get_pages(self, qs, paginate_by): + return int(ceil(Decimal(qs.count()) / Decimal(paginate_by))) + + def build_list_view(self, base_path='', qs=None, paginate_by=10): + """ + Archive Page builder that actually crawls the urls + because we need to be able to pass a request object to the template + """ + if not qs: + qs = self.get_model_queryset() + pages = self.get_pages(qs, paginate_by) + for page in range(pages): + if int(pages) > 1: + path = '%s%s/' % (base_path, str(page + 1)) + url = '%s%s/' % (base_path, str(page + 1)) + else: + path = base_path + url = base_path + response = self.client.get(url, HTTP_HOST='127.0.0.1') + if page == 0: + self.write_file(base_path, response.content) + self.write_file(path, response.content) + + def build_year_view(self, url, paginate_by=99999): + years = self.model.objects.dates('pub_date', 'year') + for year in years: + year = year.strftime('%Y') + qs = self.model.objects.filter( + status__exact=1, + pub_date__year=year + ) + self.build_list_view( + base_path=reverse(url, kwargs={'year': year, }), + qs=qs, + paginate_by=paginate_by + ) + + def build_month_view(self, url, paginate_by=99999): + months = self.model.objects.dates('pub_date', 'month') + for m in months: + year = m.strftime('%Y') + month = m.strftime('%m') + qs = self.model.objects.filter( + status__exact=1, + pub_date__year=year, + pub_date__month=month + ) + if qs.exists(): + self.build_list_view( + base_path=reverse(url, kwargs={'year': year, 'month': month}), + qs=qs, + paginate_by=paginate_by + ) + + def build_detail_view(self): + ''' + Grab all the blog posts, render them to a template + string and write that out to the filesystem + ''' + for entry in self.get_model_queryset(): + url = entry.get_absolute_url() + path, slug = os.path.split(entry.get_absolute_url()) + path = '%s/' % path + # write html + response = self.client.get(url) + self.write_file(path, response.content, filename=slug) + # write txt + response = self.client.get('%s.txt' % url) + self.write_file(path, response.content, ext='txt', filename=slug) + # write AMP + response = self.client.get('%s.amp' % url) + self.write_file(path, response.content, ext='amp', filename=slug) -#from pages.models import PageGenerator class Build(): + def write_file(self, path, text_object, ext='html', filename='index'): """ Given a path and object intended to be a webpage, write the page the diff --git a/app/jrnl/build.py b/app/jrnl/build.py new file mode 100644 index 0000000..504e288 --- /dev/null +++ b/app/jrnl/build.py @@ -0,0 +1,48 @@ +from django.core.urlresolvers import reverse +from django.apps import apps +from builder.base import BuildNew +from itertools import chain + + +class BuildJrnl(BuildNew): + + def build(self): + self.build_list_view( + base_path=reverse("jrnl:live_redirect"), + paginate_by=24 + ) + self.build_year_view("jrnl:list_year") + self.build_month_view("jrnl:list_month") + self.build_detail_view() + self.build_location_view() + + def build_location_view(self): + c = apps.get_model('locations', 'Country') + r = apps.get_model('locations', 'Region') + countries = c.objects.filter(visited=True) + regions = r.objects.all() + locations = list(chain(countries, regions)) + for c in locations: + try: + qs = self.model.objects.filter( + status__exact=1, + location__state__country=c + ) + except: + qs = self.model.objects.filter( + status__exact=1, + location__state__country__lux_region=c.id + ) + pages = self.get_pages(qs, 24) + for page in range(pages): + base_path = reverse("jrnl:list_country", kwargs={'slug': c.slug, 'page': page + 1}) + response = self.client.get(base_path, HTTP_HOST='127.0.0.1') + if page == 0: + self.write_file(base_path[:-2], response.content) + else: + self.write_file(base_path, response.content) + + +def builder(): + j = BuildJrnl("jrnl", "entry") + j.build() diff --git a/app/jrnl/models.py b/app/jrnl/models.py index fe7f274..2f84798 100644 --- a/app/jrnl/models.py +++ b/app/jrnl/models.py @@ -31,7 +31,7 @@ def image_url_replace(s): def extract_images(s): - soup = BeautifulSoup(s) + soup = BeautifulSoup(s, "lxml") imgs = [] for img in soup.find_all('img'): imgs.append(img['src']) diff --git a/app/jrnl/urls.py b/app/jrnl/urls.py index d8fa395..2b17033 100644 --- a/app/jrnl/urls.py +++ b/app/jrnl/urls.py @@ -5,12 +5,22 @@ from . import views urlpatterns = [ url( + regex=r'(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+).txt$', + view=views.EntryDetailViewTXT.as_view(), + name="detail-txt" + ), + url( + regex=r'(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+).amp$', + view=views.EntryDetailViewAMP.as_view(), + name="detail-amp" + ), + url( regex=r'(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$', view=views.EntryDetailView.as_view(), name="detail" ), url( - regex=r'^(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$', + regex=r'^(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', view=views.EntryMonthArchiveView.as_view(month_format='%m'), name="list_month" ), @@ -32,13 +42,13 @@ urlpatterns = [ # redirect /slug/ to /slug/1/ for live server url( regex=r'(?P<slug>[-\w]+)/$', - view=RedirectView.as_view(url="/jrnl/%(slug)s/1/"), + view=RedirectView.as_view(url="/jrnl/%(slug)s/1/", permanent=False), name="live_location_redirect" ), # redirect / to /1/ for live server url( regex=r'', - view=RedirectView.as_view(url="/jrnl/1/"), + view=RedirectView.as_view(url="/jrnl/1/", permanent=False), name="live_redirect" ), ] diff --git a/app/jrnl/views.py b/app/jrnl/views.py index 7dd755b..634a375 100644 --- a/app/jrnl/views.py +++ b/app/jrnl/views.py @@ -6,7 +6,7 @@ from django.conf import settings from .models import Entry, HomepageCurrator -from locations.models import Country +from locations.models import Country, Region class EntryList(ListView): @@ -38,15 +38,26 @@ class EntryCountryList(ListView): def get_context_data(self, **kwargs): # Call the base implementation first to get a context context = super(EntryCountryList, self).get_context_data(**kwargs) - context['region'] = Country.objects.get(slug__exact=self.kwargs['slug']) + try: + context['region'] = Country.objects.get(slug__exact=self.kwargs['slug']) + except: + context['region'] = Region.objects.get(slug__exact=self.kwargs['slug']) return context def get_queryset(self): - country = Country.objects.get(slug__exact=self.kwargs['slug']) - return Entry.objects.filter( - status__exact=1, - location__state__country=country - ).order_by('-pub_date') + try: + region= Country.objects.get(slug__exact=self.kwargs['slug']) + qs = Entry.objects.filter( + status__exact=1, + location__state__country=region + ).order_by('-pub_date') + except: + region = Region.objects.get(slug__exact=self.kwargs['slug']) + qs = Entry.objects.filter( + status__exact=1, + location__state__country__lux_region=region.id + ).order_by('-pub_date') + return qs class EntryYearArchiveView(YearArchiveView): @@ -70,6 +81,14 @@ class EntryDetailView(DetailView): slug_field = "slug" +class EntryDetailViewTXT(EntryDetailView): + template_name = "details/entry.txt" + + +class EntryDetailViewAMP(EntryDetailView): + template_name = "details/entry.amp" + + class HomepageList(ListView): """ Return a main entry and list of Entries in reverse chronological order diff --git a/app/photos/urls.py b/app/photos/urls.py index 9ae065d..91aac41 100644 --- a/app/photos/urls.py +++ b/app/photos/urls.py @@ -6,6 +6,6 @@ urlpatterns = patterns('', (r'data/(?P<slug>[-\w]+)/$', 'photos.views.photo_json'), (r'galleries/(?P<slug>[-\w]+)/$', 'photos.views.gallery'), (r'(?P<page>\d+)/$', 'photos.views.gallery_list'), - (r'(?P<slug>[-\w]+)/$', RedirectView.as_view(url="/photos/%(slug)s/1/")), - (r'', RedirectView.as_view(url="/photos/1/")), + (r'(?P<slug>[-\w]+)/$', RedirectView.as_view(url="/photos/%(slug)s/1/", permanent=False)), + (r'', RedirectView.as_view(url="/photos/1/", permanent=False)), ) diff --git a/design/templates/archives/writing_date.html b/design/templates/archives/writing_date.html index 7385e81..4091e74 100644 --- a/design/templates/archives/writing_date.html +++ b/design/templates/archives/writing_date.html @@ -13,7 +13,7 @@ </ul> <main role="main" id="writing-archive" class="archive"> <h1>{% if not month %}{{year|date:"Y"}}, on luxagraf{%else%} Archive: {{month|date:"F Y"}}{% endif %}</h1>{% if not month %} - <ul class="date-archive">{% regroup object_list by pub_date.month as entries_by_month %}{% for entries in entries_by_month %} + <ul class="date-archive">{% regroup object_list by pub_date.month as entries_by_month %}{% for entries in entries_by_month reversed %} <li class="dater"><span>{{ entries.list.0.pub_date|date:"F Y" }}</span> <ul>{% for post in entries.list %} <li class="arc-item"> diff --git a/design/templates/details/entry.amp b/design/templates/details/entry.amp new file mode 100644 index 0000000..6305d46 --- /dev/null +++ b/design/templates/details/entry.amp @@ -0,0 +1,175 @@ +{% load typogrify_tags %} +<!doctype html> +<html amp lang="en"> +<head> +<meta charset="utf-8"> +<title>{%block pagetitle%}{{object.title}}{%endblock%}</title> +<link rel="canonical" href="{{object.get_absolute_url}}"> + <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> + <meta name="twitter:card" content="summary_large_image"/> + <meta name="twitter:url" content="{{object.get_absolute_url}}"> + <meta name="twitter:description" content="{{object.meta_description}}"/> + <meta name="twitter:title" content="{{object.title|safe}}"/> + <meta name="twitter:site" content="@luxagraf"/> + <meta name="twitter:domain" content="luxagraf"/> + <meta name="twitter:image:src" content="{{object.get_image_url}}"/> + <meta name="twitter:creator" content="@luxagraf"/> + <meta name="twitter:site:id" content="9469062"> + <meta name="twitter:creator:id" content="9469062"> + <meta name="twitter:description" content=""/> + + <meta name="geo.placename" content="{% if object.country.name == "United States" %}{{object.location.name|smartypants|safe}}, {{object.state.name}}{%else%}{{object.location.name|smartypants|safe}}, {{object.country.name}}{%endif%}"> + <meta name="geo.region" content="{{object.country.iso2}}{%if object.state.code != '' %}-{{object.state.code}}{%endif%}"> + <meta property="og:type" content="article" /> + <meta property="og:title" content="{{object.title|safe}}" /> + <meta property="og:url" content="https://luxagraf.net{{object.get_absolute_url}}" /> + <meta property="og:description" content="{{object.meta_description}}" /> + <meta property="article:published_time" content="{{object.pub_date|date:'c'}}" /> + <meta property="article:author" content="Luxagraf" /> + <meta property="og:site_name" content="Luxagraf" /> + <meta property="og:image" content="{{object.get_image_url}}" />{% for image in object.get_images %} + <meta property="og:image" content="{{image}}" />{% endfor %} + <meta property="og:locale" content="en_US" /> + + +<script type="application/ld+json"> +{ + "@context": "http://schema.org", + "@type": "BlogPosting", + "headline": "{{object.title|safe}}", + "description": "{{object.meta_description}}", + "datePublished": "{{object.pub_date|date:"c"}}", + "author": { + "@type": "Person", + "name": "Scott Gilbertson" + }, + "publisher": { + "@type": "Person", + "name": "Jeremy Keith", + "name": "Scott Gilbertson" + "logo": { + "@type": "ImageObject", + "url": "", + "width": 240, + "height": 53 + } + } +} +</script> +<style amp-custom> +body { + font-size: 0.8125rem; + line-height: 1.73; + font-family: "Lucida Grande",Verdana,Helvetica,Arial,sans-serif; + background-color: #fff; + color: #333; + padding: 1em; +} +nav { + text-align: right; + max-width: 60em; + margin: 0 auto; +} +main { + max-width: 60em; + margin: 0 auto; +} +main footer { + text-align: right; +} +a { + text-decoration: underline; + color: #c63; +} +h1,h2,h3,h4,h5 { + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + line-height: 1; + font-weight: bold; +} +h1 { + font-size: 1.5rem; + color: #666; +} +h2 { + font-size: 1.125rem; + color: #555; +} +h3 { + font-size: 1rem; + color: #444; +} +h4 { + font-size: 0.875rem; +} +h5 { + font-size: 0.75rem; +} +h1 a, h1 a *, +h2 a, h2 a *, +h3 a, h3 a *, +h4 a, h4 a * { + text-decoration: none; + font-weight: bold +} +code { + font-size: 0.875rem; + font-family: "Courier New",monospace; +} +pre { + white-space: pre-wrap; + word-wrap: break-word; +} +blockquote { + font-family: Georgia,"Times New Roman",serif; + font-size: 0.875rem; +} +blockquote * { + font-style: italic; +} +blockquote * em { + font-weight: bold; +} +blockquote * strong { + font-style: normal; +} +hr { + border: none; + border-bottom: 0.0625rem dotted #ccc; +} +</style> +<script async custom-element="amp-audio" src="https://cdn.ampproject.org/v0/amp-audio-0.1.js"></script> +<script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script> +<style>body {opacity: 0}</style><noscript><style>body {opacity: 1}</style></noscript> +<script async src="https://cdn.ampproject.org/v0.js"></script> +</head> +<body> + +<nav> +<a href="https://luxagraf.net/"> +<amp-img src="https://adactio.com/skins/default/images/logo.png" srcset="https://adactio.com/skins/default/images/logox2.png 2x" alt="adactio" width="240" height="53" layout="fixed"></amp-img> +</a> +</nav> + +<main class="h-entry"> + <article class="h-entry hentry post--article{% with object.get_template_name_display as t %}{%if t == "double" or t == "double-dark" %} post--article--double{%endif%}{%endwith%}" itemscope itemType="http://schema.org/Article"> + <header id="header" class="post--header {% with object.get_template_name_display as t %}{%if t == "double" or t == "double-dark" %}post--header--double{%endif%}{%endwith%}"> + <h1 class="p-name entry-title post--title" itemprop="headline">{%if object.template_name == 1 or object.template_name == 3 %}{{object.title|smartypants|safe}}{%else%}{{object.title|smartypants|widont|safe}}{%endif%}</h1> + <time class="dt-published published dt-updated post--date" datetime="{{object.pub_date|date:'c'}}" itemprop="datePublished">{{object.pub_date|date:"F"}} <span>{{object.pub_date|date:"j, Y"}}</span></time> + <p class="p-author author hide" itemprop="author"><span class="byline-author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">Scott Gilbertson</span></span></p> + <aside class="p-location h-adr adr post--location" itemprop="contentLocation" itemscope itemtype="http://schema.org/Place"> + {% if object.country.name == "United States" %}<span class="p-locality locality">{{object.location.name|smartypants|safe}}</span>, <a class="p-region region" href="/jrnl/united-states/" title="travel writing from the United States">{{object.state.name|safe}}</a>, <span class="p-country-name">U.S.</span>{%else%}<span class="p-region">{{object.location.name|smartypants|safe}}</span>, <a class="p-country-name country-name" href="/jrnl/{{object.country.slug}}/" title="travel writing from {{object.country.name}}">{{object.country.name|safe}}</a>{%endif%} + <span style="display: none;" itemprop="geo" itemscope itemtype="http://schema.org/GeoCoordinates"> + <data itemprop="latitude" class="p-latitude" value="{{object.latitude}}">{{object.latitude}}</data> + <data itemprop="longitude" class="p-longitude" value="{{object.longitude}}">{{object.longitude}}</data> + </span> + {% with object.get_template_name_display as t %}{%if t == "single" or t == "single-dark" %} – <a href="" onclick="showMap({{object.latitude}}, {{object.longitude}}, { type:'point', lat:'{{object.latitude}}', lon:'{{object.longitude}}'}); return false;" title="see a map">Map</a>{%endif%}{%endwith%} + </aside> + </header> + <div id="article" class="e-content entry-content post--body post--body--{% with object.template_name as t %}{%if t == 0 or t == 2 %}single{%endif%}{%if t == 1 or t == 3 %}double{%endif%}{%endwith%}" itemprop="articleBody"> + {{object.body_html|safe|smartypants|widont}} + </div> + </article> +</main> + +</body> +</html> |