├── statirator ├── __init__.py ├── blog │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── create_post.py │ ├── templatetags │ │ ├── __init__.py │ │ └── blog.py │ ├── templates │ │ └── blog │ │ │ ├── new_post_metadata.txt │ │ │ ├── tag_list.html │ │ │ ├── new_post_content.txt │ │ │ ├── post_excerpt.html │ │ │ ├── post_list.html │ │ │ ├── tag_detail.html │ │ │ └── post_detail.html │ ├── locale │ │ └── he │ │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── sitemap.py │ ├── utils.py │ ├── renderers.py │ ├── feeds.py │ ├── views.py │ ├── readers.py │ └── models.py ├── core │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── copy_template.py │ │ │ ├── generate.py │ │ │ ├── init.py │ │ │ └── serve.py │ ├── templatetags │ │ ├── __init__.py │ │ └── st_core.py │ ├── readers.py │ ├── locale │ │ └── he │ │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── renderers.py │ ├── context_processors.py │ ├── templates │ │ ├── index.html │ │ ├── sitemap.xml │ │ ├── base.html │ │ └── 404.html │ ├── syndication.py │ ├── models.py │ ├── tests.py │ ├── parsers.py │ └── utils.py ├── pages │ ├── __init__.py │ ├── templatetags │ │ ├── __init__.py │ │ └── pages.py │ ├── templates │ │ └── pages │ │ │ └── page_detail.html │ ├── utils.py │ ├── tests.py │ ├── renderers.py │ ├── sitemap.py │ ├── views.py │ ├── models.py │ └── readers.py ├── project_template │ ├── pages │ │ ├── .gitignore │ │ └── index.html │ ├── project_name │ │ ├── __init__.py │ │ ├── urls.py │ │ └── settings.py │ ├── static │ │ ├── js │ │ │ ├── main.js │ │ │ ├── plugins.js │ │ │ └── vendor │ │ │ │ └── modernizr-2.6.1.min.js │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── img │ │ │ ├── apple-touch-icon.png │ │ │ ├── apple-touch-icon-precomposed.png │ │ │ ├── apple-touch-icon-57x57-precomposed.png │ │ │ ├── apple-touch-icon-72x72-precomposed.png │ │ │ ├── apple-touch-icon-114x114-precomposed.png │ │ │ └── apple-touch-icon-144x144-precomposed.png │ │ ├── css │ │ │ ├── normalize_rtl.css │ │ │ ├── main.css │ │ │ └── normalize.css │ │ ├── humans.txt │ │ ├── crossdomain.xml │ │ └── .htaccess │ ├── templates │ │ └── README │ ├── blog │ │ └── README │ ├── locale │ │ └── README │ └── manage.py ├── main.py ├── templates │ └── html5 │ │ ├── site.html │ │ └── modernizr.js └── test_settings.py ├── CHANGES.txt ├── .gitignore ├── requirements.txt ├── TODO ├── MANIFEST.in ├── docs ├── operation.rst ├── index.rst ├── make.bat ├── Makefile ├── quickstart.rst └── conf.py ├── LICENSE.txt ├── setup.py └── README.rst /statirator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/blog/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/core/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/blog/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/core/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/pages/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/blog/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/core/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/project_template/pages/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.1.0, 2011-04-27 -- Initial release. 2 | -------------------------------------------------------------------------------- /statirator/project_template/project_name/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /statirator/project_template/static/js/main.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .ropeproject 4 | *.egg-info 5 | docs/_build 6 | dist/* 7 | -------------------------------------------------------------------------------- /statirator/core/readers.py: -------------------------------------------------------------------------------- 1 | def dummy_reader(): 2 | "Just for tests" 3 | 4 | 5 | READERS = [dummy_reader] 6 | -------------------------------------------------------------------------------- /statirator/project_template/templates/README: -------------------------------------------------------------------------------- 1 | Overide and place templates here 2 | 3 | THIS FILE CAN BE DELETED 4 | -------------------------------------------------------------------------------- /statirator/project_template/static/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | 5 | Sitemap: /sitemap.xml 6 | -------------------------------------------------------------------------------- /statirator/blog/templates/blog/new_post_metadata.txt: -------------------------------------------------------------------------------- 1 | :slug: {{ slug }} 2 | :draft: {{ draft }} 3 | :datetime: {% now "Y-m-d H:i:s" %} 4 | -------------------------------------------------------------------------------- /statirator/blog/locale/he/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/blog/locale/he/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /statirator/core/locale/he/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/core/locale/he/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /statirator/project_template/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/project_template/static/favicon.ico -------------------------------------------------------------------------------- /statirator/project_template/static/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/project_template/static/img/apple-touch-icon.png -------------------------------------------------------------------------------- /statirator/project_template/static/img/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/project_template/static/img/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /statirator/project_template/static/css/normalize_rtl.css: -------------------------------------------------------------------------------- 1 | /* RTL overrides for normalize */ 2 | 3 | html[dir="rtl"] menu, 4 | html[dir="rtl"] ol, 5 | html[dir="rtl"] ul { 6 | padding: 0 40px 0 0; 7 | } 8 | -------------------------------------------------------------------------------- /statirator/project_template/static/img/apple-touch-icon-57x57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/project_template/static/img/apple-touch-icon-57x57-precomposed.png -------------------------------------------------------------------------------- /statirator/project_template/static/img/apple-touch-icon-72x72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/project_template/static/img/apple-touch-icon-72x72-precomposed.png -------------------------------------------------------------------------------- /statirator/project_template/static/img/apple-touch-icon-114x114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/project_template/static/img/apple-touch-icon-114x114-precomposed.png -------------------------------------------------------------------------------- /statirator/project_template/static/img/apple-touch-icon-144x144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeirKriheli/statirator/HEAD/statirator/project_template/static/img/apple-touch-icon-144x144-precomposed.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pygments==1.6 2 | distribute==0.6.38 3 | docutils==0.10 4 | html5writer==0.1 5 | django-medusa==0.1.dev 6 | Django==1.4.1 7 | django-taggit 8 | django-disqus==0.5 9 | django-bidi-utils 10 | BeautifulSoup 11 | -------------------------------------------------------------------------------- /statirator/pages/templates/pages/page_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %}{% load i18n st_core %} 2 | 3 | {% block title %}{{ object.title }}{% endblock %} 4 | 5 | {% block content %} 6 | {{ object.content|safe }} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Sitemaps 2 | * Setup logging and convert print statements 3 | * Prefix default language as well 4 | * Multi domains for each lanugage 5 | * Documentation 6 | * Allow translation of metadata keywords and directives in rst 7 | * Testing 8 | -------------------------------------------------------------------------------- /statirator/project_template/blog/README: -------------------------------------------------------------------------------- 1 | Blog posts go in here. 2 | 3 | To easily create a new multilingual blog post with the title, run:: 4 | 5 | ./manage.py create_post "English title goes here" 6 | 7 | Once created, edit and add content. 8 | 9 | 10 | THIS FILE CAN BE DELETED 11 | -------------------------------------------------------------------------------- /statirator/project_template/static/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | jQuery, Modernizr 16 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.rst 2 | recursive-include docs *.txt *.rst 3 | recursive-include statirator/templates * 4 | recursive-include statirator/project_template * 5 | recursive-include statirator/core/templates * 6 | recursive-include statirator/blog/templates * 7 | recursive-include statirator/blog/locale * 8 | -------------------------------------------------------------------------------- /statirator/project_template/locale/README: -------------------------------------------------------------------------------- 1 | Translation go here. Use:: 2 | 3 | ./manage.py makemessages 4 | 5 | From the project's root dir to create the translations. Once translated, use 6 | 7 | ./manage.py compilemessages 8 | 9 | To compile the translation files. 10 | 11 | THIS FILE CAN BE DELETED 12 | -------------------------------------------------------------------------------- /statirator/project_template/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /statirator/pages/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import os 3 | 4 | 5 | def get_pages_dir(): 6 | "Returns the pages directory from settings, or default one if not found" 7 | 8 | from django.conf import settings 9 | 10 | return getattr(settings, 'PAGES_DIR', 11 | os.path.join(settings.ROOT_DIR, 'pages')) 12 | -------------------------------------------------------------------------------- /statirator/core/renderers.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.core.urlresolvers import reverse 4 | from django_medusa.renderers import StaticSiteRenderer 5 | 6 | 7 | class CoreRenderer(StaticSiteRenderer): 8 | 9 | def get_paths(self): 10 | 11 | paths = [reverse('sitemap')] 12 | 13 | return paths 14 | 15 | 16 | renderers = [CoreRenderer] 17 | -------------------------------------------------------------------------------- /statirator/core/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.sites.models import Site 3 | 4 | 5 | def st_settings(request): 6 | """Return stuff """ 7 | 8 | return { 9 | 'GOOGLE_ANALYTICS_ID': getattr(settings, 'GOOGLE_ANALYTICS_ID', None), 10 | 'POSTS_IN_INDEX': getattr(settings, 'POSTS_IN_INDEX', 5), 11 | 'site': Site.objects.get_current() 12 | } 13 | -------------------------------------------------------------------------------- /statirator/pages/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /statirator/core/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %}{% load i18n st_core blog %} 2 | 3 | {% block title %}{% trans "Welcome" %}{% endblock %} 4 | 5 | {% block content %} 6 |

{% trans "Welcome" %}

7 | {% get_recent_posts LANGUAGE_CODE POSTS_IN_INDEX as posts %} 8 | {% for post in posts %}{% include "blog/post_excerpt.html" %}{% endfor %} 9 |

{% trans "More posts at the archive" %}

10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /statirator/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def main(): 6 | if 'test' in sys.argv: 7 | os.environ.setdefault( 8 | "DJANGO_SETTINGS_MODULE", "statirator.test_settings") 9 | else: 10 | from django.conf import settings 11 | settings.configure(INSTALLED_APPS=('statirator.core', )) 12 | 13 | from django.core import management 14 | management.execute_from_command_line() 15 | 16 | 17 | if __name__ == '__main__': 18 | main() 19 | -------------------------------------------------------------------------------- /statirator/project_template/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %}{% load i18n st_core blog %} 2 | 3 | {% block title %}{% trans "Welcome" %}{% endblock %} 4 | 5 | {% block description %}{% trans "Welcome to index page" %}{% endblock %} 6 | 7 | {% block content %} 8 | 13 | {% get_recent_posts LANGUAGE_CODE POSTS_IN_INDEX as posts %} 14 | {% for post in posts %}{% include "blog/post_excerpt.html" %}{% endfor %} 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /statirator/blog/templates/blog/tag_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %}{% load i18n st_core blog %} 2 | 3 | {% block title %}{% trans "Tags" %}{% endblock %} 4 | 5 | {% block body_class %}tags{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |

{% trans "Tags" %}

11 |
12 | {% get_current_language as LANGUAGE_CODE %}{% get_tagcloud LANGUAGE_CODE as tags %} 13 |
    14 | {% for tag in tags %} 15 |
  • {{ tag.name }}
  • 16 | {% endfor %} 17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /statirator/templates/html5/site.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}{% end %} - {{ site.name }} 6 | 7 | 8 | 9 | {% block extra_head %}{% end %} 10 | 11 | 12 |

{{ site.name }}

13 |
{% block content %}{% end %}
14 |
footer goes here
15 | 16 | 17 | -------------------------------------------------------------------------------- /statirator/pages/renderers.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.conf import settings 4 | from django.utils.translation import activate 5 | from django_medusa.renderers import StaticSiteRenderer 6 | 7 | from .models import Page 8 | 9 | 10 | class PagesRenderer(StaticSiteRenderer): 11 | 12 | def get_paths(self): 13 | 14 | paths = [] 15 | 16 | for lang_code, lang_name in settings.LANGUAGES: 17 | activate(lang_code) 18 | items = Page.objects.filter(language=lang_code) 19 | paths.extend([i.get_absolute_url() for i in items]) 20 | 21 | return paths 22 | 23 | 24 | renderers = [PagesRenderer] 25 | -------------------------------------------------------------------------------- /statirator/project_template/static/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /statirator/blog/templates/blog/new_post_content.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% get_current_language as LANGUAGE_CODE %}{% get_language_info for LANGUAGE_CODE as lang %} 2 | {% trans "Tag" as tag_i18n %}{% trans "Title" as title_i18n %} 3 | ============================================================= 4 | {% if LANGUAGE_CODE == 'en' %}{{title|safe}}{% else %}{% blocktrans with lang.name_local as lang_name %}{{lang_name}} {{ title_i18n }}{% endblocktrans %}{% endif %} 5 | ============================================================= 6 | 7 | :lang: {{ LANGUAGE_CODE }} 8 | :tags: {{ tag_i18n }} 1|tag-1, {{ tag_i18n }} 2|tag-2 9 | 10 | {% blocktrans with lang.name_local as lang_name %}{{ lang_name }} content goes here{% endblocktrans %} 11 | -------------------------------------------------------------------------------- /statirator/project_template/static/js/plugins.js: -------------------------------------------------------------------------------- 1 | // Avoid `console` errors in browsers that lack a console. 2 | if (!(window.console && console.log)) { 3 | (function() { 4 | var noop = function() {}; 5 | var methods = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'markTimeline', 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn']; 6 | var length = methods.length; 7 | var console = window.console = {}; 8 | while (length--) { 9 | console[methods[length]] = noop; 10 | } 11 | }()); 12 | } 13 | 14 | // Place any jQuery/helper plugins in here. 15 | -------------------------------------------------------------------------------- /statirator/core/syndication.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.syndication.views import Feed 3 | 4 | 5 | class LanguageFeed(Feed): 6 | """Stores the request language in `language_code` attribute, and sets 7 | the feed's language to the apropriate one. 8 | """ 9 | 10 | def get_feed(self, obj, request): 11 | "Override to get items per language" 12 | 13 | try: 14 | self.language_code = request.LANGUAGE_CODE # store it for later 15 | except AttributeError: 16 | self.language_code = settings.LANGUAGE_CODE 17 | 18 | feed = super(LanguageFeed, self).get_feed(obj, request) 19 | feed.feed['language'] = self.language_code 20 | 21 | return feed 22 | -------------------------------------------------------------------------------- /statirator/pages/sitemap.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.contrib.sitemaps import Sitemap 4 | from django.conf import settings 5 | 6 | from .models import Page 7 | 8 | 9 | class PagesSiteMap(Sitemap): 10 | 11 | def items(self): 12 | "Return the items starting with defualt language and it's index page" 13 | 14 | default = settings.LANGUAGE_CODE 15 | langs = [default] + [x[0] for x in settings.LANGUAGES if x[0] != default] 16 | 17 | items = [] 18 | for lang in langs: 19 | idx = Page.objects.get(slug='index', language=lang) 20 | 21 | items.append(idx) 22 | 23 | items.extend(Page.objects.filter(language=lang).exclude(slug='index')) 24 | 25 | return items 26 | -------------------------------------------------------------------------------- /statirator/blog/templates/blog/post_excerpt.html: -------------------------------------------------------------------------------- 1 | {% load i18n disqus_tags %} 2 | {% with post_url=post.get_absolute_url %} 3 | 19 | -------------------------------------------------------------------------------- /statirator/pages/views.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.views.generic.detail import DetailView 4 | from django.template import Template 5 | from django.template.response import TemplateResponse 6 | from .models import Page 7 | 8 | 9 | class PageView(DetailView): 10 | 11 | model = Page 12 | 13 | def get_queryset(self): 14 | 15 | qs = Page.objects.filter(language=self.request.LANGUAGE_CODE) 16 | return qs 17 | 18 | def render_to_response(self, context, **response_kwargs): 19 | # if this is html content, it's a template, and we should render it 20 | if self.object.page_type == 'html': 21 | t = Template(self.object.content) 22 | return TemplateResponse(self.request, t, context) 23 | 24 | return super(self, PageView).render_to_response( 25 | context, **response_kwargs) 26 | -------------------------------------------------------------------------------- /statirator/core/locale/he/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2012-10-08 18:02+0200\n" 12 | "PO-Revision-Date: 2012-10-08 18:02+0200\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 20 | 21 | #: templates/index.html:3 templates/index.html.py:6 22 | msgid "Welcome" 23 | msgstr "ברוכים הבאים" 24 | 25 | #: templates/index.html:9 26 | msgid "More posts at the archive" 27 | msgstr "עוד פוסטים בארכיון" 28 | -------------------------------------------------------------------------------- /statirator/blog/templates/blog/post_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %}{% load i18n st_core %} 2 | 3 | {% block title %}{% trans "Archive" %}{% endblock %} 4 | 5 | {% block extra_head %} 6 | {% get_current_language as LANGUAGE_CODE %} 7 | 8 | {% endblock %} 9 | 10 | {% block body_class %}blog{% endblock %} 11 | 12 | {% block content %} 13 |
14 |
15 |

{% trans "Archive" %}

16 |
17 | {% regroup object_list by pubdate|date:"Y" as year_posts %} 18 |
    19 | {% for year in year_posts %} 20 |
  • {{ year.grouper }}

  • 21 |
      22 | {% for post in year.list %} 23 |
    • {{ post.pubdate|date:"b d" }}, {{LANGUAGE_MARKER}}{{ post.title }}
    • 24 | {% endfor %} 25 |
    26 | {% endfor %} 27 |
28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /docs/operation.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Modus operandi 3 | ================== 4 | 5 | Init 6 | ============ 7 | 8 | The init command creates the basic project using a `custom project template`_. 9 | ``settings.py`` and ``urls.py`` are under ``conf`` dir. 10 | 11 | .. _custom project template: https://docs.djangoproject.com/en/dev/releases/1.4/#custom-project-and-app-templates 12 | 13 | 14 | Generating the site 15 | ====================== 16 | 17 | The following steps are done when generating the site 18 | 19 | * Sync in memory db 20 | * Create the site enrty for the `Sites` app. 21 | * Read the resources (posts, pages) into the db using Readers_. 22 | * Generate the static pages for those (and other) resources with Renderes_. 23 | * Copy the static media to the build directory. 24 | 25 | .. _Sites: https://docs.djangoproject.com/en/dev/ref/contrib/sites/ 26 | 27 | 28 | Readers 29 | ============= 30 | 31 | 32 | 33 | Renderes 34 | ============= 35 | 36 | 37 | Static media 38 | ============= 39 | -------------------------------------------------------------------------------- /statirator/blog/sitemap.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.contrib.sitemaps import Sitemap 4 | from django.conf import settings 5 | from django.core.urlresolvers import reverse 6 | 7 | from statirator.core.models import DummyTranslation 8 | from .models import Post, I18NTag 9 | 10 | 11 | class BlogSiteMap(Sitemap): 12 | 13 | def items(self): 14 | "Return the items starting with defualt language and it's index page" 15 | 16 | default = settings.LANGUAGE_CODE 17 | langs = [default] + [x[0] for x in settings.LANGUAGES if x[0] != default] 18 | 19 | items = [] 20 | for lang in langs: 21 | for slug in ('blog_archive', 'blog_tags'): 22 | dummy = DummyTranslation(None, lang, slug, reverse(slug)) 23 | items.append(dummy) 24 | items.extend(Post.objects.filter(language=lang, is_published=True)) 25 | items.extend(I18NTag.objects.filter(language=lang)) 26 | 27 | return items 28 | -------------------------------------------------------------------------------- /statirator/blog/templates/blog/tag_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %}{% load i18n %} 2 | 3 | {% block title %}{{ object.name }} :: {% trans "Tag" %}{% endblock %} 4 | 5 | {% block extra_head %} 6 | 7 | {% endblock %} 8 | 9 | {% block body_class %}tags{% endblock %} 10 | 11 | {% block content %} 12 |
13 |
14 |

{% blocktrans with object.name as tag_name %}Posts tagged with {{ tag_name }}{% endblocktrans %}

15 |
16 | {% regroup posts by pubdate|date:"Y" as year_posts %} 17 |
    18 | {% for year in year_posts %} 19 |
  • {{ year.grouper }}

  • 20 |
      21 | {% for post in year.list %} 22 |
    • {{ post.pubdate|date:"b d" }}, {{LANGUAGE_MARKER}}{{ post.title }}
    • 23 | {% endfor %} 24 |
    25 | {% endfor %} 26 |
27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2012 by Meir Kriheli 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /statirator/core/templates/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | {% spaceless %} 7 | {% for url in urlset %} 8 | 9 | {{ url.location }} 10 | {% if url.lastmod %} 11 | {{ url.lastmod|date:"Y-m-d" }} 12 | {% elif url.item.pubdate %} 13 | {{ url.item.pubdate|date:"Y-m-d" }} 14 | {% endif %} 15 | {% if url.changefreq %}{{ url.changefreq }}{% endif %} 16 | {% if url.priority %}{{ url.priority }}{% endif %} 17 | {% if url.item.image %} 18 | 19 | http://{{ site.domain }}/{{ url.item.image }} 20 | 21 | {% endif %} 22 | {% for t in url.item.get_translations %} 23 | 24 | {% endfor %} 25 | 26 | {% endfor %} 27 | {% endspaceless %} 28 | 29 | -------------------------------------------------------------------------------- /statirator/pages/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | 4 | from statirator.core.utils import i18n_permalink 5 | from statirator.core.models import TranslationsMixin 6 | 7 | PAGE_TYPES = ( 8 | ('rst', 'reStructured Text'), 9 | ('html', 'Django template'), 10 | ) 11 | 12 | 13 | class Page(models.Model, TranslationsMixin): 14 | """A multilingual page""" 15 | 16 | title = models.CharField(max_length=200) 17 | slug = models.SlugField(max_length=200) 18 | content = models.TextField() 19 | language = models.CharField(max_length=5, choices=settings.LANGUAGES, 20 | blank=True, default=settings.LANGUAGE_CODE) 21 | page_type = models.CharField(max_length=5, choices=PAGE_TYPES) 22 | description = models.CharField(max_length=255, blank=True, null=True) 23 | 24 | def __unicode__(self): 25 | return self.title or self.slug 26 | 27 | @i18n_permalink 28 | def get_absolute_url(self): 29 | # index is a special case 30 | if self.slug == 'index': 31 | return ('pages_index', (), {}) 32 | 33 | return ('pages_page', (), {'slug': self.slug}) 34 | -------------------------------------------------------------------------------- /statirator/blog/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import os 3 | 4 | 5 | def get_blog_dir(): 6 | "Returns the blog directory from settings, or default one if not found" 7 | 8 | from django.conf import settings 9 | 10 | return getattr(settings, 'BLOG_DIR', 11 | os.path.join(settings.ROOT_DIR, 'blog')) 12 | 13 | 14 | _post_keys = None 15 | 16 | 17 | def get_post_urlpattern_keys(): 18 | """Returns the keys defined for the blog's url pattern, to correctly 19 | calculate the post's permalink""" 20 | 21 | global _post_keys 22 | 23 | if _post_keys is None: 24 | from django.core import urlresolvers 25 | 26 | conf = urlresolvers.get_urlconf() 27 | resolver = urlresolvers.get_resolver(conf) 28 | 29 | try: 30 | # get the 1st pattern matching 'blog_post' 31 | post_re_pattern = ( 32 | x for x in resolver.url_patterns 33 | if getattr(x, 'name', None) == 'blog_post' 34 | ).next() 35 | except StopIteration: 36 | raise Exception('URL pattern named "blog_post" not found') 37 | 38 | _post_keys = post_re_pattern.regex.groupindex.keys() 39 | 40 | return _post_keys 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='statirator', 5 | version='0.2.0', 6 | author='Meir Kriheli', 7 | author_email='mkriheli@gmail.com', 8 | packages=['statirator'], 9 | url='http://pypi.python.org/pypi/statirator/', 10 | license='LICENSE.txt', 11 | description='Multilingual static site and blog generator', 12 | long_description=open('README.rst').read(), 13 | zip_safe=False, 14 | install_requires=[ 15 | 'setuptools', 16 | 'docutils', 17 | 'Pygments', 18 | 'html5writer', 19 | 'Django==1.6.5', 20 | 'django-taggit>=0.12', 21 | 'django-medusa', 22 | 'django-disqus', 23 | 'django-bidi-utils', 24 | 'BeautifulSoup', 25 | ], 26 | dependency_links=[ 27 | 'https://github.com/MeirKriheli/rst-to-semantic-html5/tarball/master#egg=html5writer', 28 | 'https://github.com/mtigas/django-medusa/tarball/master#egg=django-medusa', 29 | ], 30 | entry_points={ 31 | 'console_scripts': [ 32 | 'statirator = statirator.main:main', 33 | ] 34 | }, 35 | classifiers=[ 36 | 'License :: OSI Approved :: MIT License', 37 | 'Programming Language :: Python', 38 | 'Operating System :: OS Independent', 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /statirator/test_settings.py: -------------------------------------------------------------------------------- 1 | # Generated by statirator 2 | import os 3 | 4 | # directories setup 5 | 6 | ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) 7 | SOURCE_DIR = os.path.join(ROOT_DIR, '') 8 | BUILD_DIR = os.path.join(ROOT_DIR, 'build') 9 | 10 | DATABASES = { 11 | 'default': { 12 | 'ENGINE': 'django.db.backends.sqlite3', 13 | 'NAME': ':memory:', 14 | } 15 | } 16 | 17 | SITE_ID = 1 18 | 19 | # Local time zone. Choices can be found here: 20 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 21 | # although not all choices may be available on all operating systems. 22 | # In a Windows environment this must be set to your system time zone. 23 | TIME_ZONE = 'America/Chicago' 24 | 25 | # Default Language code. All choices can be found here: 26 | # http://www.i18nguy.com/unicode/language-identifiers.html 27 | LANGUAGE_CODE = 'he' 28 | _ = lambda s: s 29 | 30 | LANGUAGES = ( 31 | ('en', _('English')), 32 | ('he', _('Hebrew')), 33 | ) 34 | 35 | #ROOT_URLCONF = 'conf.urls' 36 | 37 | TEMPLATE_DIRS = ( 38 | os.path.join(ROOT_DIR, 'templates'), 39 | ) 40 | 41 | INSTALLED_APPS = ( 42 | 'django.contrib.contenttypes', 43 | 'django.contrib.sites', 44 | 'django.contrib.staticfiles', 45 | 'django_medusa', 46 | 'taggit', 47 | 'statirator.core', 48 | 'statirator.blog', 49 | ) 50 | 51 | MEDUSA_RENDERER_CLASS = "django_medusa.renderers.DiskStaticSiteRenderer" 52 | MEDUSA_MULTITHREAD = True 53 | MEDUSA_DEPLOY_DIR = BUILD_DIR 54 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Statirator 2 | ============ 3 | 4 | Multilingual static site and blog generator. 5 | 6 | Reason 7 | -------- 8 | 9 | * Needed good multilingual static site generator, which enables: 10 | 11 | * Blog posts in reStructuredText_ 12 | * Single resource (file) for all the translations of that post or page 13 | * Explicitly specifying slug for various non-Latin links in addition to posts 14 | (e.g: Tag names, pages, etc.) 15 | * Separate RSS feeds for each language and each tag/language 16 | * Keeps reference between the translations 17 | * Optional Multi-domain support - One for each language (TODO) 18 | * Translated elements in pages 19 | 20 | * No need to reinvent the wheel: 21 | 22 | * Many know Django_, we can reuse the knowledge 23 | * Make use of reusable apps 24 | * Hack around i18n bits of Django_. 25 | * Use Django_'s `Internationalization and localization`_ 26 | 27 | 28 | For users 29 | ----------- 30 | 31 | See the `Statirator documentation`_ at Read the Docs. 32 | 33 | .. _Statirator documentation: https://statirator.readthedocs.org/en/latest/ 34 | 35 | 36 | For developers: 37 | -------------------- 38 | 39 | * Create a virtualenv (python2 till django moves to 3) 40 | * Clone the repo 41 | * run ``python setup.py develop`` 42 | 43 | To build the docs: 44 | 45 | * install Sphinx_ 46 | * ``cd docs; make html`` 47 | 48 | 49 | .. _Sphinx: http://sphinx.pocoo.org/ 50 | .. _Django: https://www.djangoproject.com/ 51 | .. _Internationalization and localization: https://docs.djangoproject.com/en/1.4/topics/i18n/ 52 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html 53 | -------------------------------------------------------------------------------- /statirator/blog/renderers.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.conf import settings 4 | from django.utils.translation import activate 5 | from django_medusa.renderers import StaticSiteRenderer 6 | 7 | from statirator.core.utils import i18n_reverse 8 | from .models import Post, I18NTag 9 | 10 | 11 | class BlogRenderer(StaticSiteRenderer): 12 | 13 | def get_paths(self): 14 | 15 | paths = [] 16 | for lang_code, lang_name in settings.LANGUAGES: 17 | # posts 18 | activate(lang_code) 19 | items = Post.objects.filter( 20 | is_published=True, language=lang_code).order_by('-pubdate') 21 | 22 | paths.extend([i.get_absolute_url() for i in items]) 23 | 24 | paths.append(i18n_reverse(lang_code, 'blog_archive')) 25 | paths.append(i18n_reverse(lang_code, 'blog_feed')) 26 | 27 | return paths 28 | 29 | 30 | class TagsRenderer(StaticSiteRenderer): 31 | 32 | def get_paths(self): 33 | 34 | paths = [] 35 | for lang_code, lang_name in settings.LANGUAGES: 36 | activate(lang_code) 37 | items = list( 38 | I18NTag.objects.filter(language=lang_code).order_by('name')) 39 | 40 | paths.extend([i.get_absolute_url() for i in items]) 41 | 42 | for tag in items: 43 | paths.append(i18n_reverse(lang_code, 'blog_tag_feed', 44 | kwargs={'slug': tag.slug_no_locale})) 45 | 46 | paths.append(i18n_reverse(lang_code, 'blog_tags')) 47 | 48 | return paths 49 | 50 | 51 | renderers = [BlogRenderer, TagsRenderer] 52 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Statirator documentation master file, created by 2 | sphinx-quickstart on Sun Sep 23 01:34:44 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Statirator's documentation! 7 | ====================================== 8 | 9 | Multilingual static site and blog generator. 10 | 11 | Reason 12 | -------- 13 | 14 | * Needed good multilingual static site generator, which enables: 15 | 16 | * Blog post in reStructuredText_ 17 | * Single resource (file) for all the translations of that post or page 18 | * Explicitly specifying slug for various non-Latin links in addition to posts 19 | (e.g: Tag names, pages, etc.) 20 | * Separate RSS feeds for each language and each tag/language 21 | * Keeps reference between the translations 22 | * Optional Multi-domain support - One for each language (TODO) 23 | * Translated elements in pages 24 | 25 | * No need to reinvent the wheel: 26 | 27 | * Many know Django_, we can reuse the knowledge 28 | * Make use of reusable apps 29 | * Hack around i18n bits of Django_. 30 | * Use Django_'s `Internationalization and localization`_ 31 | 32 | 33 | Source Code 34 | ------------ 35 | 36 | For the project from GitHub: https://github.com/MeirKriheli/statirator 37 | 38 | Contents: 39 | 40 | .. _Django: https://www.djangoproject.com/ 41 | .. _Internationalization and localization: https://docs.djangoproject.com/en/1.4/topics/i18n/ 42 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html 43 | 44 | .. toctree:: 45 | :maxdepth: 2 46 | 47 | quickstart 48 | operation 49 | 50 | 51 | 52 | Indices and tables 53 | ================== 54 | 55 | * :ref:`genindex` 56 | * :ref:`modindex` 57 | * :ref:`search` 58 | 59 | -------------------------------------------------------------------------------- /statirator/project_template/project_name/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | from django.conf.urls.i18n import i18n_patterns 3 | from django.conf import settings 4 | from statirator.blog import views, feeds 5 | from statirator.pages import views as pviews, sitemap as psitemap 6 | from statirator.blog import sitemap as bsitemap 7 | 8 | # we need to make sure the pages slug won't catch the /en/ etc for index pages 9 | # in various languages 10 | langs_re = '|'.join(x[0] for x in settings.LANGUAGES) 11 | 12 | sitemaps = { 13 | 'pages': psitemap.PagesSiteMap, 14 | 'blog': bsitemap.BlogSiteMap, 15 | } 16 | 17 | urlpatterns = patterns( 18 | '', 19 | url(r'^(?P\d{4})/(?P\d{2})/(?P[-\w]+)/$', 20 | views.PostView.as_view(), name='blog_post'), 21 | url(r'^archive/$', views.ArchiveView.as_view(), name='blog_archive'), 22 | url(r'^blog\.rss$', feeds.PostsFeed(), name='blog_feed'), 23 | url(r'^tags/$', views.TagsView.as_view(), name='blog_tags'), 24 | url(r'^tags/(?P[-\w]+)/$', views.TagView.as_view(), name='blog_tag'), 25 | url(r'^tags/(?P[-\w]+)/tag.rss$', feeds.TagFeed(), 26 | name='blog_tag_feed'), 27 | # keep those last 28 | url(r'^$', pviews.PageView.as_view(), {'slug': 'index'}, name='pages_index'), 29 | url(r'^(?P(?!%s)[-\w]+)/$' % langs_re, pviews.PageView.as_view(), 30 | name='pages_page'), 31 | ) 32 | 33 | # make all the urls patterns again, with i18n translations, that way default 34 | # language is not prefixed 35 | 36 | urlpatterns += i18n_patterns( 37 | '', 38 | *[url(x._regex, x.callback, x.default_args, name='i18n_' + x.name) 39 | for x in urlpatterns] 40 | ) 41 | 42 | urlpatterns += patterns('django.contrib.sitemaps.views', 43 | url(r'^sitemap.xml$', 'sitemap', {'sitemaps': sitemaps}, name='sitemap'), 44 | ) 45 | -------------------------------------------------------------------------------- /statirator/core/management/commands/copy_template.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | 5 | from django.core.management.base import BaseCommand, CommandError 6 | from django.conf import settings 7 | from django.template.base import TemplateDoesNotExist 8 | from django.template.loader import find_template_loader 9 | 10 | 11 | class Command(BaseCommand): 12 | 13 | help = "Copy a template into templates dir to override and extend it" 14 | args = "" 15 | 16 | def handle(self, *args, **options): 17 | if not args: 18 | raise CommandError('Please specify at least one template name') 19 | 20 | loaders = [] 21 | for loader_name in settings.TEMPLATE_LOADERS: 22 | loader = find_template_loader(loader_name) 23 | if loader is not None: 24 | loaders.append(loader) 25 | 26 | # make sure we have the loaders 27 | for name in args: 28 | source = display_name = None 29 | for loader in loaders: 30 | try: 31 | source, display_name = loader.load_template_source(name, None) 32 | except TemplateDoesNotExist: 33 | pass 34 | 35 | if source is None: 36 | raise CommandError('Template {0} not found'.format(name)) 37 | 38 | full_name = os.path.abspath( 39 | os.path.join(settings.TEMPLATE_DIRS[0], name)) 40 | 41 | head, tail = os.path.split(full_name) 42 | 43 | if head: 44 | try: 45 | os.makedirs(head) 46 | except OSError: 47 | pass 48 | 49 | with open(full_name, 'w') as tmpl: 50 | tmpl.write(source) 51 | 52 | print('Copied template {0}'.format(name)) 53 | -------------------------------------------------------------------------------- /statirator/core/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from django.conf import settings 3 | from .utils import path_to_lang, LANGS_DICT 4 | 5 | 6 | class TranslationsMixin(object): 7 | "Helper for getting transalations" 8 | 9 | SLUG_FIELD_FOR_TRANSLATIONS = 'slug' # Overide in models if needed 10 | LANG_FIELD_FOR_TRANSLATIONS = 'language' # Overide in models if needed 11 | 12 | def get_translations(self): 13 | "Query set for the translations" 14 | 15 | self_slug = getattr(self, self.SLUG_FIELD_FOR_TRANSLATIONS) 16 | self_lang = getattr(self, self.LANG_FIELD_FOR_TRANSLATIONS) 17 | 18 | slug = {self.SLUG_FIELD_FOR_TRANSLATIONS + '__exact': self_slug} 19 | lang = {self.LANG_FIELD_FOR_TRANSLATIONS + '__exact': self_lang} 20 | return self.__class__.objects.filter(**slug).exclude(**lang) 21 | 22 | def get_language(self): 23 | "Get the language display for this item's language" 24 | 25 | attr = 'get_{0}_display'.format(self.LANG_FIELD_FOR_TRANSLATIONS) 26 | return getattr(self, attr)() 27 | 28 | 29 | class DummyTranslation(object): 30 | """Dummy translations for views to put in template context in case there's no 31 | actual object""" 32 | 33 | def __init__(self, request, language=None, title=None, path=None): 34 | self.title = title 35 | self.request = request 36 | self.language = language or request.LANGUAGE_CODE 37 | self.path = path or request.path 38 | 39 | def get_translations(self): 40 | for code, name in settings.LANGUAGES: 41 | if code != self.language: 42 | yield DummyTranslation(self.request, code, name, self.path) 43 | 44 | def get_language(self): 45 | return LANGS_DICT.get(self.language) 46 | 47 | def get_absolute_url(self): 48 | return path_to_lang(self.path, self.language) 49 | -------------------------------------------------------------------------------- /statirator/blog/feeds.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.shortcuts import get_object_or_404 4 | from django.utils.translation import ugettext as _ 5 | 6 | from .models import Post, I18NTag 7 | from statirator.core.syndication import LanguageFeed 8 | from statirator.core.utils import i18n_reverse, content_absolute_links 9 | 10 | 11 | class PostsFeed(LanguageFeed): 12 | 13 | def title(self): 14 | return _("Recent posts") 15 | 16 | def link(self): 17 | return i18n_reverse(self.language_code, 'blog_archive') 18 | 19 | def items(self): 20 | return Post.objects.filter( 21 | language=self.language_code, 22 | is_published=True).order_by('-pubdate')[:10] 23 | 24 | def item_title(self, item): 25 | return item.title 26 | 27 | def item_pubdate(self, item): 28 | return item.pubdate 29 | 30 | def item_description(self, item): 31 | return content_absolute_links(item.content, item.image) 32 | 33 | def item_categories(self, item): 34 | return item.tags.values_list('name', flat=True) 35 | 36 | 37 | class TagFeed(LanguageFeed): 38 | 39 | def get_object(self, request, slug): 40 | return get_object_or_404( 41 | I18NTag, slug_no_locale=slug, 42 | language=request.LANGUAGE_CODE) 43 | 44 | def title(self, obj): 45 | return _('Posts tagged with %(tag_name)s') % {'tag_name': obj.name} 46 | 47 | def items(self, obj): 48 | return Post.objects.filter( 49 | language=obj.language, 50 | is_published=True, 51 | tags__slug__in=[obj.slug]).order_by('-pubdate') 52 | 53 | def link(self, obj): 54 | return obj.get_absolute_url() 55 | 56 | def item_title(self, item): 57 | return item.title 58 | 59 | def item_pubdate(self, item): 60 | return item.pubdate 61 | 62 | def item_description(self, item): 63 | return content_absolute_links(item.content, item.image) 64 | -------------------------------------------------------------------------------- /statirator/core/management/commands/generate.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import logging 3 | 4 | from django.core.management import call_command 5 | from django.core.management.base import BaseCommand 6 | from statirator.core.utils import find_readers 7 | 8 | 9 | class Command(BaseCommand): 10 | 11 | help = "Walk the resources, builds the db, and generates the static site" 12 | 13 | def handle(self, *args, **options): 14 | 15 | self.syncdb() 16 | self.create_sites() 17 | self.read_resources() 18 | self.gen_static() 19 | self.collect_static_media() 20 | 21 | def print_title(self, title): 22 | print() 23 | print(title) 24 | print(len(title) * '-') 25 | 26 | def syncdb(self): 27 | self.print_title('Syncing in memory db') 28 | # make sure we have the db 29 | call_command('syncdb', load_initial_data=False, interactive=False) 30 | 31 | def create_sites(self): 32 | "Make sure we have the site framework setup correctly" 33 | from django.conf import settings 34 | from django.contrib.sites.models import Site 35 | 36 | Site.objects.all().delete() 37 | 38 | for idx, (domain, lang, title) in enumerate(settings.SITES, 1): 39 | site = Site(domain=domain, name=title, pk=idx) 40 | site.save() 41 | 42 | def read_resources(self): 43 | """Walk to readers to populate the db""" 44 | 45 | readers = find_readers() 46 | self.print_title('Reading resource') 47 | 48 | for reader in readers: 49 | logging.info('Reading with %s', reader) 50 | reader() 51 | 52 | def gen_static(self): 53 | self.print_title('Generating static pages') 54 | 55 | # Use django-medusa for static pages 56 | call_command('staticsitegen') 57 | 58 | def collect_static_media(self): 59 | self.print_title('Collecting static media') 60 | call_command('collectstatic', interactive=False) 61 | -------------------------------------------------------------------------------- /statirator/blog/templates/blog/post_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %}{% load i18n disqus_tags %} 2 | 3 | {% block title %}{{ object.title }}{% endblock %} 4 | 5 | {% block description %}{{ object.excerpt }}{% endblock %} 6 | 7 | {% block body_class %}blog{% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |

{{ object.title }}

13 | 17 |
18 | {% if object.image %}
{{ object.title  }}
{% endif %} 19 | {{ object.content|safe }} 20 |
21 | 22 | 27 | {% with object.get_translations as translations %} 28 | {% if translations %} 29 |
{% trans "Translations" %}:{{ LANGUAGE_MARKER }}
30 |
    31 | {% for tr in translations %} 32 |
  • {{ tr.title }}
  • 33 | {% endfor %} 34 |
35 | {% endif %} 36 | {% endwith %} 37 | 45 |
46 | {% set_disqus_identifier object.slug "_" object.language %} 47 | 52 | {% disqus_show_comments %} 53 |
54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /statirator/pages/readers.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, absolute_import 2 | 3 | import os 4 | from django.conf import settings 5 | from django.template import Template, RequestContext 6 | from django.utils import translation 7 | from django.test.client import RequestFactory 8 | 9 | from statirator.core.utils import find_files, render_block_to_string 10 | from .utils import get_pages_dir 11 | from .models import Page 12 | 13 | 14 | def update_post(slug, lang_code, defaults): 15 | with translation.override(lang_code): 16 | page, created = Page.objects.get_or_create( 17 | slug=slug, 18 | language=lang_code, 19 | defaults=defaults) 20 | 21 | if not created: 22 | for field, val in defaults.iteritems(): 23 | setattr(page, field, val) 24 | # get the title from the template 25 | t = Template(page.content) 26 | req = RequestFactory().get(page.get_absolute_url(), LANGUAGE_CODE=lang_code) 27 | page.title = render_block_to_string( 28 | t, 'title', context_instance=RequestContext(req)) 29 | page.description = render_block_to_string( 30 | t, 'description', context_instance=RequestContext(req)) 31 | 32 | page.save() 33 | 34 | 35 | def html_reader(): 36 | "Finds django html pages, parses them and loads into the db." 37 | 38 | for page in find_files(get_pages_dir(), ['.html']): 39 | print('Processing {0}'.format(page)) 40 | slug, ext = os.path.splitext(os.path.basename(page)) 41 | 42 | with open(page) as p: 43 | template_content = p.read() 44 | 45 | # we create one for each language. Less efficient, but will work we 46 | # i18n_permalink without further hacking 47 | # 48 | # Each template will be renderd for each language, so make sure to 49 | # have language logic in the template 50 | for lang_code, lang_name in settings.LANGUAGES: 51 | defaults = dict(content=template_content, page_type='html') 52 | update_post(slug, lang_code, defaults) 53 | 54 | 55 | READERS = [html_reader] 56 | -------------------------------------------------------------------------------- /statirator/blog/views.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.views.generic.detail import DetailView 4 | from django.views.generic.list import ListView 5 | from django.views.generic.base import TemplateView 6 | from statirator.core.models import DummyTranslation 7 | 8 | from .models import Post, I18NTag 9 | 10 | 11 | class PostView(DetailView): 12 | 13 | model = Post 14 | 15 | def get_queryset(self): 16 | qs = Post.objects.filter(language=self.request.LANGUAGE_CODE) 17 | return qs 18 | 19 | 20 | class ArchiveView(ListView): 21 | 22 | model = Post 23 | 24 | def get_queryset(self): 25 | qs = Post.objects.filter(language=self.request.LANGUAGE_CODE, 26 | is_published=True).order_by('-pubdate') 27 | return qs 28 | 29 | def get_context_data(self, **kwargs): 30 | # Add dummy object for translations in menu to display translation to 31 | # this view 32 | ctx = super(ArchiveView, self).get_context_data(**kwargs) 33 | ctx['object'] = DummyTranslation(self.request) 34 | return ctx 35 | 36 | 37 | class TagView(DetailView): 38 | 39 | model = I18NTag 40 | template_name = 'blog/tag_detail.html' 41 | 42 | def get_object(self): 43 | 44 | return I18NTag.objects.get(language=self.request.LANGUAGE_CODE, 45 | slug_no_locale=self.kwargs['slug']) 46 | 47 | def get_context_data(self, **kwargs): 48 | 49 | ctx = super(TagView, self).get_context_data(**kwargs) 50 | 51 | tag = ctx['object'] 52 | 53 | ctx['posts'] = Post.objects.filter( 54 | language=self.request.LANGUAGE_CODE, 55 | is_published=True, 56 | tags__slug__in=[tag.slug]).order_by('-pubdate') 57 | 58 | return ctx 59 | 60 | 61 | class TagsView(TemplateView): 62 | 63 | template_name = 'blog/tag_list.html' 64 | 65 | def get_context_data(self, **kwargs): 66 | # Add dummy object for translations in menu to display translation to 67 | # this view 68 | ctx = super(TagsView, self).get_context_data(**kwargs) 69 | ctx['object'] = DummyTranslation(self.request) 70 | return ctx 71 | -------------------------------------------------------------------------------- /statirator/blog/locale/he/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PACKAGE VERSION\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2012-11-08 17:39+0200\n" 11 | "PO-Revision-Date: 2012-11-08 17:39+0200\n" 12 | "Last-Translator: Meir Kriheli \n" 13 | "Language-Team: Hebrew \n" 14 | "Language: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 19 | 20 | #: feeds.py:14 21 | msgid "Recent posts" 22 | msgstr "פוסטים אחרונים" 23 | 24 | #: feeds.py:45 templates/blog/tag_detail.html:6 25 | #: templates/blog/tag_detail.html.py:14 26 | #, python-format 27 | msgid "Posts tagged with %(tag_name)s" 28 | msgstr "פוסטים מתוייגים תחת %(tag_name)s" 29 | 30 | #: models.py:25 31 | msgid "Slug without locale" 32 | msgstr "" 33 | 34 | #: templates/blog/new_post_content.txt:2 templates/blog/tag_detail.html:3 35 | msgid "Tag" 36 | msgstr "תג" 37 | 38 | #: templates/blog/new_post_content.txt:2 39 | msgid "Title" 40 | msgstr "כותרת" 41 | 42 | #: templates/blog/new_post_content.txt:4 43 | #, python-format 44 | msgid "%(lang_name)s %(title_i18n)s" 45 | msgstr "%(title_i18n)s %(lang_name)s" 46 | 47 | #: templates/blog/new_post_content.txt:10 48 | #, python-format 49 | msgid "%(lang_name)s content goes here" 50 | msgstr "תוכן %(lang_name)s יבוא כאן" 51 | 52 | #: templates/blog/post_detail.html:14 53 | msgid "Published" 54 | msgstr "פורסם" 55 | 56 | #: templates/blog/post_detail.html:21 templates/blog/tag_list.html:3 57 | #: templates/blog/tag_list.html.py:10 58 | msgid "Tags" 59 | msgstr "תגים" 60 | 61 | #: templates/blog/post_detail.html:29 62 | msgid "Translations" 63 | msgstr "תרגומים" 64 | 65 | #: templates/blog/post_excerpt.html:14 66 | msgid "Read more" 67 | msgstr "קראו עוד" 68 | 69 | #: templates/blog/post_excerpt.html:15 70 | msgid "Comments" 71 | msgstr "תגובות" 72 | 73 | #: templates/blog/post_list.html:3 templates/blog/post_list.html.py:7 74 | #: templates/blog/post_list.html:15 75 | msgid "Archive" 76 | msgstr "ארכיון" 77 | -------------------------------------------------------------------------------- /statirator/blog/management/commands/create_post.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import codecs 4 | import os 5 | 6 | from django.conf import settings 7 | from django.core.management.base import BaseCommand, CommandError 8 | from django.template.defaultfilters import slugify 9 | from django.template.loader import render_to_string 10 | from django.utils import translation 11 | from optparse import make_option 12 | 13 | from statirator.blog.utils import get_blog_dir 14 | 15 | 16 | class Command(BaseCommand): 17 | 18 | args = '' 19 | help = 'Create a new rst blog post' 20 | 21 | option_list = ( 22 | make_option( 23 | '--draft', '-d', dest='draft', default=False, action='store_true', 24 | help='Is is a draft (unpublished) ? [Default: "%default"]'), 25 | ) + BaseCommand.option_list 26 | 27 | def handle(self, *args, **options): 28 | 29 | if len(args) != 1: 30 | raise CommandError('Single argument of English title or slug ' 31 | 'is required') 32 | 33 | title_or_slug = args[0] 34 | slug = slugify(title_or_slug) 35 | draft = int(options['draft']) 36 | 37 | ctx = { 38 | 'slug': slug, 39 | 'draft': draft, 40 | } 41 | 42 | filename = os.path.join(get_blog_dir(), slug + '.rst') 43 | 44 | if os.path.exists(filename): 45 | raise CommandError('"{0}" exists, aborting'.format(filename)) 46 | with codecs.open(filename, 'w', 'utf-8') as post_file: 47 | 48 | meta_rendered = render_to_string('blog/new_post_metadata.txt', ctx) 49 | post_file.write(meta_rendered) 50 | 51 | cur_lang = translation.get_language() 52 | 53 | # now we need to render for each language 54 | for lang_code, lang_name in settings.LANGUAGES: 55 | post_file.write('\n.. --') 56 | translation.activate(lang_code) 57 | 58 | ctx.update({'title': title_or_slug}) 59 | rendered = render_to_string('blog/new_post_content.txt', ctx) 60 | post_file.write(rendered) 61 | 62 | translation.activate(cur_lang) 63 | 64 | print("Created post", os.path.relpath(filename)) 65 | -------------------------------------------------------------------------------- /statirator/core/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from django.utils import unittest 4 | from statirator.core.utils import find_readers 5 | from statirator.core.readers import dummy_reader 6 | from statirator.core.parsers import parse_rst 7 | 8 | 9 | TEST_DOC = """ 10 | :slug: some-post-title-slugified 11 | :draft: 1 12 | :datetime: 2012-09-12 16:03:15 13 | 14 | This will be ignored in main meta section 15 | 16 | .. -- 17 | 18 | ================= 19 | English title 20 | ================= 21 | 22 | :lang: en 23 | :tags: Tag1, Tag2 24 | 25 | The content of the English post 26 | 27 | And another paragraph 28 | 29 | .. -- 30 | 31 | ==================== 32 | כותרת עברית 33 | ==================== 34 | 35 | :lang: he 36 | :tags: פייתון|python, Heb Tag2|slug 37 | 38 | The content of the post in Hebrew 39 | """.decode('utf-8') 40 | 41 | 42 | class CoreTestCase(unittest.TestCase): 43 | 44 | def test_find_readers(self): 45 | "Correctly find readers" 46 | 47 | readers = find_readers() 48 | self.assertIn(dummy_reader, readers) 49 | 50 | def test_rst_parser(self): 51 | """Correctly parse multilingual rst documents""" 52 | 53 | parsed = parse_rst(TEST_DOC) 54 | generic_metadata, title, content = parsed.next() 55 | self.assertEqual(generic_metadata, { 56 | 'slug': 'some-post-title-slugified', 57 | 'draft': True, 58 | 'datetime': datetime.datetime(2012, 9, 12, 16, 3, 15), 59 | }) 60 | self.assertEqual(content.strip(), 61 | u'

This will be ignored in main meta section

') 62 | 63 | en_metadata, en_title, en_content = parsed.next() 64 | self.assertEqual(en_metadata, {'lang': 'en', 'tags': ['Tag1', 'Tag2']}) 65 | self.assertEqual(en_title, u'English title') 66 | self.assertEqual(en_content.strip(), 67 | u'

The content of the English post

\n' 68 | u'

And another paragraph

') 69 | 70 | he_metadata, he_title, he_content = parsed.next() 71 | self.assertEqual(he_metadata, { 72 | 'lang': 'he', 73 | 'tags': ['פייתון|python'.decode('utf-8'), 'Heb Tag2|slug'] 74 | }) 75 | self.assertEqual(he_title, 'כותרת עברית'.decode('utf-8')) 76 | self.assertEqual(he_content.strip(), 77 | u'

The content of the post in Hebrew

') 78 | -------------------------------------------------------------------------------- /statirator/blog/readers.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, absolute_import 2 | 3 | from django.template.defaultfilters import slugify 4 | from statirator.core.utils import find_files 5 | from statirator.core.parsers import parse_rst 6 | from .utils import get_blog_dir 7 | from .models import I18NTag, Post 8 | 9 | 10 | def rst_reader(): 11 | "Finds rst posts, parses them and loads into the db." 12 | 13 | for post in find_files(get_blog_dir(), ['.rst']): 14 | print('Processing {0}'.format(post)) 15 | with open(post) as p: 16 | parsed = parse_rst(p.read()) 17 | 18 | generic_metadata, title, content = parsed.next() 19 | 20 | # got those, now go over the languages 21 | for metadata, title, content in parsed: 22 | lang = metadata['lang'] 23 | 24 | tags = [] 25 | for meta_tag in metadata['tags']: 26 | try: 27 | name, slug = meta_tag.split('|') 28 | except ValueError: 29 | name, slug = meta_tag, slugify(meta_tag) 30 | 31 | i18n_slug = '{0}-{1}'.format(lang, slug) 32 | 33 | tag, created = I18NTag.objects.get_or_create( 34 | slug=i18n_slug, language=lang, 35 | defaults={'name': name, 'slug_no_locale': slug}) 36 | 37 | if not created: 38 | tag.name = name 39 | tag.slug_no_locale = slug 40 | tag.save() 41 | 42 | tags.append(tag) 43 | 44 | defaults = dict( 45 | title=title, 46 | is_published=not generic_metadata['draft'], 47 | content=content, 48 | pubdate=generic_metadata['datetime'], 49 | excerpt=metadata.get('excerpt'), 50 | image=generic_metadata.get('image')) 51 | 52 | post, created = Post.objects.get_or_create( 53 | slug=generic_metadata['slug'], 54 | language=lang, defaults=defaults) 55 | 56 | if not created: 57 | for field, val in defaults.iteritems(): 58 | setattr(post, field, val) 59 | post.save() 60 | 61 | post.tags.set(*tags) 62 | 63 | READERS = [rst_reader] 64 | -------------------------------------------------------------------------------- /statirator/pages/templatetags/pages.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from django.template.base import Node, Library, TemplateSyntaxError 4 | from statirator.pages.models import Page 5 | 6 | register = Library() 7 | 8 | 9 | class PagesNode(Node): 10 | """Pages list""" 11 | 12 | def __init__(self, language, asvar): 13 | self.language = language 14 | self.asvar = asvar 15 | 16 | def render(self, context): 17 | language = self.language.resolve(context) 18 | 19 | pages = Page.objects.filter(language=language) 20 | 21 | context[self.asvar] = pages 22 | return '' 23 | 24 | 25 | class PageNode(Node): 26 | "Get a single page" 27 | 28 | def __init__(self, language, slug, asvar): 29 | self.language = language 30 | self.slug = slug 31 | self.asvar = asvar 32 | 33 | def render(self, context): 34 | language = self.language.resolve(context) 35 | slug = self.slug.resolve(context) 36 | 37 | try: 38 | page = Page.objects.get(language=language, slug=slug) 39 | except Page.DoesNotExist: 40 | page = None 41 | 42 | context[self.asvar] = page 43 | return '' 44 | 45 | 46 | @register.tag 47 | def get_pages(parser, token): 48 | """Returns all pages. 49 | 50 | The first argument is the language code. specify "as" var e.g:: 51 | 52 | {% get_pages LANGUAGE_CODE as pages_list %} 53 | 54 | """ 55 | bits = token.split_contents() 56 | 57 | if len(bits) != 4 and bits[2] != 'as': 58 | raise TemplateSyntaxError("Usage: %s language_code as" 59 | " context_var" % bits[0]) 60 | 61 | language = parser.compile_filter(bits[1]) 62 | asvar = bits[-1] 63 | 64 | return PagesNode(language, asvar) 65 | 66 | 67 | @register.tag 68 | def get_page(parser, token): 69 | """Returns a page base on slug and language. 70 | 71 | The first argument is the language code, 2nd is slug. specify "as" var e.g:: 72 | 73 | {% get_page LANGUAGE_CODE "index" as index_page %} 74 | 75 | """ 76 | bits = token.split_contents() 77 | 78 | if len(bits) != 5 and bits[3] != 'as': 79 | raise TemplateSyntaxError('Usage: %s language_code "slug" as' 80 | " context_var" % bits[0]) 81 | 82 | language = parser.compile_filter(bits[1]) 83 | slug = parser.compile_filter(bits[2]) 84 | asvar = bits[-1] 85 | 86 | return PageNode(language, slug, asvar) 87 | -------------------------------------------------------------------------------- /statirator/core/parsers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | from datetime import datetime 4 | from docutils.core import publish_doctree, publish_from_doctree 5 | from docutils.nodes import docinfo 6 | from html5writer import html5writer 7 | 8 | 9 | FIELDS = { 10 | 'slug': None, 11 | 'title': None, 12 | 'lang': None, 13 | 'draft': lambda x: bool(int(x)), 14 | 'tags': lambda x: [y.strip() for y in x.split(',')], 15 | 'datetime': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S'), 16 | 'excerpt': None, 17 | 'image': None, 18 | } 19 | 20 | DEFAULTS = { 21 | 'syntax_highlight': 'short', 22 | } 23 | 24 | 25 | def parse_rst(content): 26 | """Parse multilingual rst document. Content should contain a metadata 27 | section which is the same for all languages, and sections for each 28 | language. the sections should be separated by comment of "--"". e.g:: 29 | 30 | :slug: some-post-title-slugified 31 | :draft: 1 32 | :datetime: 2012-09-12 16:03:15 33 | :excerpt: Short description 34 | :image: /img/some_image.png 35 | 36 | This will be ignored in main meta section 37 | 38 | .. -- 39 | 40 | ================= 41 | English title 42 | ================= 43 | 44 | :lang: en 45 | :tags: Tag1, Tag2 46 | 47 | The content of the English post 48 | 49 | And another paragraph 50 | 51 | .. -- 52 | 53 | ==================== 54 | כותרת עברית 55 | ==================== 56 | 57 | :lang: he 58 | :tags: פייתון|python, Heb Tag2|slug 59 | 60 | The content of the post in Hebrew 61 | 62 | Returned value is a genearator:: 63 | 64 | (common metadata, '', content), 65 | (metadata, title, content), 66 | (metadata, title, content) ... 67 | """ 68 | 69 | parts = re.split(r'^\.\.\s+--\s*$', content, flags=re.M) 70 | 71 | for part in parts: 72 | content = '' 73 | title = '' 74 | metadata = {} 75 | 76 | tree = publish_doctree(part, settings_overrides=DEFAULTS) 77 | 78 | for info in tree.traverse(docinfo): 79 | for field in info.children: 80 | name_el, body_el = field.children 81 | name = name_el.astext().lower() 82 | if name in FIELDS: 83 | body = body_el.astext() 84 | transform = FIELDS[name] 85 | 86 | metadata[name] = transform(body) if transform else body 87 | 88 | writer = html5writer.SemanticHTML5Writer() 89 | publish_from_doctree(tree, writer=writer) 90 | content = writer.parts['body'] 91 | title = writer.parts['title'] 92 | yield metadata, title, content 93 | -------------------------------------------------------------------------------- /statirator/core/management/commands/init.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | import sys 5 | 6 | from django.core.management.base import BaseCommand, CommandError 7 | from optparse import make_option 8 | 9 | 10 | class Command(BaseCommand): 11 | 12 | help = "Init the static site project" 13 | 14 | args = '[directory]' 15 | 16 | option_list = ( 17 | make_option( 18 | '--title', '-t', dest='title', default='Default site', 19 | help='Site title [Default: "%default"]'), 20 | make_option( 21 | '--domain', '-d', dest='domain', default='example.com', 22 | help='Domain name [Default: "%default"]'), 23 | make_option( 24 | '--languages', '-l', dest='languages', default='he,en', 25 | help='Supported languages. [Default: "%default"]'), 26 | make_option( 27 | '--timezone', '-z', dest='timezone', default='America/Chicago', 28 | help='Time Zone. [Default: "%default"]'), 29 | ) + BaseCommand.option_list 30 | 31 | def handle(self, *args, **options): 32 | 33 | if len(args) != 1: 34 | raise CommandError('init takes one argument: directory name') 35 | 36 | directory = args[0] 37 | 38 | from django.conf.global_settings import LANGUAGES 39 | 40 | the_langs = dict(LANGUAGES) 41 | 42 | langs = options.pop('languages').split(',') 43 | 44 | # we need to keep ordering, for the default language 45 | try: 46 | languages = [(l, the_langs[l]) for l in langs] 47 | except KeyError, e: 48 | print("Invalid language specified:", e, end=". ") 49 | print("Valid languages are:") 50 | for name, desc in LANGUAGES: 51 | print(name, '(' + desc + ')') 52 | sys.exit(1) 53 | 54 | os.makedirs(directory) 55 | print("Initializing project structure in", directory) 56 | 57 | extra = { 58 | 'build': 'build', 59 | 'default_lang': langs[0], 60 | 'languages': languages, 61 | 'extensions': ('py', ), 62 | 'domain': options['domain'], 63 | 'timezone': options['timezone'], 64 | 'title': options['title'], 65 | 'files': (), 66 | 'template': os.path.abspath( 67 | os.path.join( 68 | os.path.dirname(__file__), 69 | os.pardir, os.pardir, os.pardir, 'project_template')), 70 | } 71 | extra.update(options) 72 | 73 | from django.core.management import call_command 74 | call_command('startproject', 'conf', directory, **extra) 75 | 76 | print("\n\tdomain:", options['domain']) 77 | print("\ttimezone:", options['timezone']) 78 | print("\ttitle:", options['title']) 79 | print("\tlanguages:", ', '.join(langs)) 80 | -------------------------------------------------------------------------------- /statirator/project_template/project_name/settings.py: -------------------------------------------------------------------------------- 1 | # Generated by statirator 2 | import os 3 | 4 | # Directories setup 5 | ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) 6 | BUILD_DIR = os.path.join(ROOT_DIR, '{{ build }}') 7 | 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.sqlite3', 11 | 'NAME': ':memory:', 12 | } 13 | } 14 | 15 | 16 | # Site(s) definitions. The Sites will be created when generate is called 17 | # Each site is (domain, language, title). language of None means all 18 | SITES = ( 19 | ('{{ domain }}', None, '{{ title }}'), 20 | ) 21 | 22 | SITE_ID = 1 23 | 24 | # Local time zone. Choices can be found here: 25 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 26 | # although not all choices may be available on all operating systems. 27 | # In a Windows environment this must be set to your system time zone. 28 | TIME_ZONE = '{{ timezone }}' 29 | 30 | # Default Language code. All choices can be found here: 31 | # http://www.i18nguy.com/unicode/language-identifiers.html 32 | LANGUAGE_CODE = '{{ default_lang }}' 33 | _ = lambda s: s 34 | 35 | LANGUAGES = ({% for code, name in languages %} 36 | ('{{ code }}', _('{{ name }}')), 37 | {% endfor %}) 38 | 39 | ROOT_URLCONF = '{{ project_name }}.urls' 40 | 41 | TEMPLATE_DIRS = ( 42 | os.path.join(ROOT_DIR, 'templates'), 43 | ) 44 | 45 | MIDDLEWARE_CLASSES = ( 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.locale.LocaleMiddleware', 48 | ) 49 | 50 | 51 | TEMPLATE_CONTEXT_PROCESSORS = ( 52 | 'django.core.context_processors.debug', 53 | 'django.core.context_processors.i18n', 54 | 'django.core.context_processors.media', 55 | 'django.core.context_processors.static', 56 | 'django.core.context_processors.tz', 57 | 'statirator.core.context_processors.st_settings', 58 | 'bidiutils.context_processors.bidi', 59 | ) 60 | 61 | LOCALE_PATHS = ( 62 | os.path.join(ROOT_DIR, 'locale'), 63 | ) 64 | 65 | # Static files setup 66 | STATIC_URL = '/' 67 | STATIC_ROOT = BUILD_DIR 68 | STATICFILES_DIRS = ( 69 | os.path.join(ROOT_DIR, 'static'), 70 | ) 71 | 72 | INSTALLED_APPS = ( 73 | 'django.contrib.contenttypes', 74 | 'django.contrib.sites', 75 | 'django.contrib.staticfiles', 76 | 'django_medusa', 77 | 'taggit', 78 | 'disqus', 79 | 'statirator.core', 80 | 'statirator.blog', 81 | 'statirator.pages', 82 | ) 83 | 84 | MEDUSA_RENDERER_CLASS = "django_medusa.renderers.DiskStaticSiteRenderer" 85 | MEDUSA_MULTITHREAD = False 86 | MEDUSA_DEPLOY_DIR = BUILD_DIR 87 | 88 | # Set your Analytics site id in here to enable analytics in pages 89 | GOOGLE_ANALYTICS_ID = '' 90 | 91 | # How many posts to show on the index page 92 | POSTS_IN_INDEX = 2 93 | 94 | # disqus settings 95 | DISQUS_API_KEY = '' 96 | DISQUS_WEBSITE_SHORTNAME = '' 97 | 98 | # Compensate for some changes in django 1.6 99 | SECRET_KEY = 'dummy' 100 | ALLOWED_HOSTS = ['*'] 101 | 102 | # Optionally place sensitive settings, which shoulen't be tracked by version 103 | # control (e.g: Secret API keys, etc) in local_settings.py. Make sure to have 104 | # that file ignored. 105 | try: 106 | from local_settings import * 107 | except ImportError: 108 | pass 109 | -------------------------------------------------------------------------------- /statirator/core/management/commands/serve.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, absolute_import 2 | 3 | import os 4 | import re 5 | import time 6 | import threading 7 | 8 | import BaseHTTPServer 9 | import SimpleHTTPServer 10 | import SocketServer 11 | from django.conf import settings 12 | from django.core.management import call_command 13 | from django.core.management.base import NoArgsCommand, BaseCommand 14 | from optparse import make_option 15 | 16 | from statirator.core.utils import filesystem_changed 17 | 18 | 19 | class ThreadedHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): 20 | "Threaded basic http server" 21 | 22 | 23 | class Command(NoArgsCommand): 24 | 25 | help = "Build and serve the static site" 26 | 27 | option_list = ( 28 | make_option('--port', '-p', dest='port', default=8000, type='int', 29 | help='The port to listen [Default: %default]'), 30 | make_option('--auto-generate', '-g', dest='generate', 31 | action='store_true', help='Auto generate static site on ' 32 | 'each modification'), 33 | ) + BaseCommand.option_list 34 | 35 | def handle_noargs(self, **options): 36 | 37 | self.httpd_thread = None 38 | self.httpd_server = None 39 | self._shutdown = False 40 | curr_dir = os.getcwd() 41 | 42 | try: 43 | if options['generate']: 44 | self.filesystem_watcher() 45 | else: 46 | call_command('generate') 47 | 48 | os.chdir(settings.BUILD_DIR) 49 | self.serve(options['port']) 50 | 51 | while not self._shutdown: 52 | try: 53 | time.sleep(1) 54 | except KeyboardInterrupt: 55 | self.shutdown() 56 | finally: 57 | os.chdir(curr_dir) 58 | 59 | def shutdown(self): 60 | print("\nShutting down") 61 | self._shutdown = True 62 | self.httpd_server.shutdown() 63 | self.httpd_thread.join() 64 | 65 | def filesystem_watcher(self): 66 | lock = threading.Lock() 67 | 68 | from django.conf import settings 69 | 70 | ignore_re = None 71 | to_ignore = ['\.pyc'] 72 | 73 | pipeline_css = getattr(settings, 'PIPELINE_CSS', None) 74 | for k, v in pipeline_css.iteritems(): 75 | source_filenames = v.get('source_filenames', []) 76 | for res in source_filenames: 77 | if res.endswith('.less'): 78 | to_ignore.append(res.replace('.less', '\.css')) 79 | 80 | if to_ignore: 81 | ignore_re = re.compile('.*(' + '|'.join(to_ignore) + ')$') 82 | 83 | def watch(): 84 | while not self._shutdown: 85 | with lock: 86 | if filesystem_changed( 87 | settings.ROOT_DIR, 88 | ignore_dirs=[settings.BUILD_DIR], 89 | ignore_re=ignore_re): 90 | call_command('generate') 91 | time.sleep(1) 92 | 93 | watcher_thread = threading.Thread(target=watch) 94 | watcher_thread.start() 95 | 96 | def serve(self, port): 97 | self.httpd_server = ThreadedHTTPServer( 98 | ('', port), SimpleHTTPServer.SimpleHTTPRequestHandler) 99 | 100 | self.httpd_thread = threading.Thread(target=self.httpd_server.serve_forever) 101 | self.httpd_thread.setDaemon(True) 102 | self.httpd_thread.start() 103 | -------------------------------------------------------------------------------- /statirator/core/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n staticfiles st_core pages %}{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block title %}{% endblock %} :: {{ site.name }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% if LANGUAGE_BIDI %} {% endif %} 21 | 22 | {% if object %} {% endif %} 23 | 24 | {% block extra_head %}{% endblock %} 25 | 26 | 27 |
28 |

{{ site.name }}

29 | 40 |
41 | 44 | 45 | {% block content %}{% endblock %} 46 | 47 |
48 | Footer 49 |
50 | 51 | 52 | 53 | 54 | {% if GOOGLE_ANALYTICS_ID %} 55 | 61 | {% endif %} 62 | {% block extra_footer %}{% endblock %} 63 | 64 | 65 | -------------------------------------------------------------------------------- /statirator/core/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 |
    148 |
  • a mistyped address
  • 149 |
  • an out-of-date link
  • 150 |
151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /statirator/blog/templatetags/blog.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from django.conf import settings 3 | from django.db.models import Count 4 | from django.template.base import Node, Library, TemplateSyntaxError 5 | 6 | from statirator.blog.models import I18NTag, Post 7 | 8 | register = Library() 9 | 10 | # tag cloud from django-taggit-templatetags: 11 | # https://github.com/feuervogel/django-taggit-templatetags 12 | 13 | T_MAX = getattr(settings, 'TAGCLOUD_MAX', 6.0) 14 | T_MIN = getattr(settings, 'TAGCLOUD_MIN', 1.0) 15 | 16 | 17 | def get_queryset(language): 18 | qs = I18NTag.objects.filter(language=language) 19 | qs = qs.annotate(num_times=Count('blog_i18ntaggeditem_items')) 20 | return qs 21 | 22 | 23 | def get_weight_fun(t_min, t_max, f_min, f_max): 24 | def weight_fun(f_i, t_min=t_min, t_max=t_max, f_min=f_min, f_max=f_max): 25 | # Prevent a division by zero here, found to occur under some 26 | # pathological but nevertheless actually occurring circumstances. 27 | if f_max == f_min: 28 | mult_fac = 1.0 29 | else: 30 | mult_fac = float(t_max - t_min) / float(f_max - f_min) 31 | 32 | return t_max - (f_max - f_i) * mult_fac 33 | return weight_fun 34 | 35 | 36 | class TagListNode(Node): 37 | "Simple tag list, ordered by count" 38 | 39 | def __init__(self, language, asvar): 40 | self.language = language 41 | self.asvar = asvar 42 | 43 | def render(self, context): 44 | language = self.language.resolve(context) 45 | tags_qs = get_queryset(language).order_by('-num_times') 46 | 47 | if self.asvar: 48 | context[self.asvar] = tags_qs 49 | return '' 50 | else: 51 | return tags_qs 52 | 53 | 54 | class TagCloudNode(Node): 55 | "Tag cloud list, ordered by name, has weight attribute" 56 | 57 | def __init__(self, language, asvar): 58 | self.language = language 59 | self.asvar = asvar 60 | 61 | def render(self, context): 62 | language = self.language.resolve(context) 63 | tags_qs = get_queryset(language).order_by('-num_times') 64 | 65 | num_times = tags_qs.values_list('num_times', flat=True) 66 | 67 | if num_times: 68 | weight_fun = get_weight_fun(T_MIN, T_MAX, min(num_times), 69 | max(num_times)) 70 | tags_qs = tags_qs.order_by('name') 71 | for tag in tags_qs: 72 | tag.weight = weight_fun(tag.num_times) 73 | 74 | context[self.asvar] = tags_qs 75 | return '' 76 | 77 | 78 | class RecentPostsNode(Node): 79 | """Recent posts for a specific language""" 80 | 81 | def __init__(self, language, count, asvar=None): 82 | self.language = language 83 | self.count = count 84 | self.asvar = asvar 85 | 86 | def render(self, context): 87 | language = self.language.resolve(context) 88 | count = self.count.resolve(context) 89 | 90 | qs = Post.objects.filter( 91 | language=language, is_published=True).order_by('-pubdate')[:count] 92 | 93 | if self.asvar: 94 | context[self.asvar] = qs 95 | return '' 96 | else: 97 | return qs 98 | 99 | 100 | @register.tag 101 | def get_taglist(parser, token): 102 | """Returns a tag list, sorted bt the number of tagged items. 103 | 104 | The first argument is the language code. specify "as" var e.g:: 105 | 106 | {% get_taglist LANGUAGE_CODE as tags_list %} 107 | 108 | """ 109 | bits = token.split_contents() 110 | 111 | if len(bits) != 4 and bits[2] != 'as': 112 | raise TemplateSyntaxError("Usage: '%s' language_code as" 113 | " context_var" % bits[0]) 114 | 115 | language = parser.compile_filter(bits[1]) 116 | asvar = bits[-1] 117 | 118 | return TagListNode(language, asvar) 119 | 120 | 121 | @register.tag 122 | def get_tagcloud(parser, token): 123 | """Returns a tag cloud, sorted by name and has the "weight" attribute. 124 | 125 | The first argument is the language code. specify "as" var e.g:: 126 | 127 | {% get_taglist LANGUAGE_CODE as tags_list %} 128 | 129 | """ 130 | bits = token.split_contents() 131 | 132 | if len(bits) != 4 and bits[2] != 'as': 133 | raise TemplateSyntaxError("Usage: '%s' language_code as" 134 | " context_var" % bits[0]) 135 | 136 | language = parser.compile_filter(bits[1]) 137 | asvar = bits[-1] 138 | 139 | return TagCloudNode(language, asvar) 140 | 141 | 142 | @register.tag 143 | def get_recent_posts(parser, token): 144 | """Returns num of recent posts for a specific language 145 | 146 | 147 | The first argument is the language code, 2nd is count. Optionally specify 148 | "as" var e.g:: 149 | 150 | {% get_taglist LANGUAGE_CODE as tags_list %} 151 | 152 | """ 153 | bits = token.split_contents() 154 | 155 | if len(bits) < 3: 156 | raise TemplateSyntaxError("'%s' takes at least language_code and count" 157 | " parameters" % bits[0]) 158 | 159 | language = parser.compile_filter(bits[1]) 160 | count = parser.compile_filter(bits[2]) 161 | 162 | bits = bits[3:] 163 | 164 | asvar = None 165 | if bits and bits[-2] == 'as': 166 | asvar = bits[-1] 167 | 168 | return RecentPostsNode(language, count, asvar) 169 | -------------------------------------------------------------------------------- /statirator/blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models, IntegrityError 2 | from django.conf import settings 3 | from django.utils.translation import ugettext_lazy as _ 4 | from django.utils.encoding import python_2_unicode_compatible 5 | from taggit.managers import TaggableManager 6 | from taggit.models import GenericTaggedItemBase, TagBase, atomic 7 | 8 | from statirator.core.utils import i18n_permalink 9 | from statirator.core.models import TranslationsMixin 10 | 11 | 12 | @python_2_unicode_compatible 13 | class I18NTag(models.Model, TranslationsMixin): 14 | """Extend Taggit's Tag: 15 | 16 | * Add a language field 17 | * slug will be appended the locale, since we can't override the uniqute in 18 | the abstract model 19 | * slug_no_locale will have the actual slug 20 | 21 | """ 22 | 23 | SLUG_FIELD_FOR_TRANSLATIONS = 'slug_no_locale' # we need to override this 24 | 25 | name = models.CharField(verbose_name=_('Name'), max_length=100) 26 | slug = models.SlugField(verbose_name=_('Slug'), unique=True, 27 | max_length=100) 28 | language = models.CharField(max_length=5, choices=settings.LANGUAGES, 29 | blank=True, default=settings.LANGUAGE_CODE) 30 | slug_no_locale = models.SlugField(verbose_name=_('Slug without locale'), 31 | unique=False, max_length=100) 32 | 33 | class Meta: 34 | unique_together = ('language', 'slug_no_locale') 35 | 36 | @i18n_permalink 37 | def get_absolute_url(self): 38 | return ('blog_tag', (), {'slug': self.slug_no_locale}) 39 | 40 | @i18n_permalink 41 | def get_feed_url(self): 42 | return ('blog_tag_feed', (), {'slug': self.slug_no_locale}) 43 | 44 | def save(self, *args, **kwargs): 45 | if not self.pk and not self.slug: 46 | self.slug = self.slugify(self.name) 47 | from django.db import router 48 | using = kwargs.get("using") or router.db_for_write( 49 | type(self), instance=self) 50 | # Make sure we write to the same db for all attempted writes, 51 | # with a multi-master setup, theoretically we could try to 52 | # write and rollback on different DBs 53 | kwargs["using"] = using 54 | # Be oportunistic and try to save the tag, this should work for 55 | # most cases ;) 56 | try: 57 | with atomic(using=using): 58 | res = super(I18NTag, self).save(*args, **kwargs) 59 | return res 60 | except IntegrityError: 61 | pass 62 | # Now try to find existing slugs with similar names 63 | slugs = set(I18NTag.objects.filter(slug__startswith=self.slug) 64 | .values_list('slug', flat=True)) 65 | i = 1 66 | while True: 67 | slug = self.slugify(self.name, i) 68 | if slug not in slugs: 69 | self.slug = slug 70 | # We purposely ignore concurrecny issues here for now. 71 | # (That is, till we found a nice solution...) 72 | return super(I18NTag, self).save(*args, **kwargs) 73 | i += 1 74 | else: 75 | return super(I18NTag, self).save(*args, **kwargs) 76 | 77 | __str__ = TagBase.__str__ 78 | slugify = TagBase.slugify 79 | 80 | 81 | class I18NTaggedItem(GenericTaggedItemBase): 82 | tag = models.ForeignKey(I18NTag, 83 | related_name="%(app_label)s_%(class)s_items") 84 | 85 | 86 | class Post(models.Model, TranslationsMixin): 87 | """Multilingual blog posts""" 88 | 89 | title = models.CharField(max_length=200) 90 | slug = models.SlugField(max_length=200) 91 | is_published = models.BooleanField(default=True, max_length=200) 92 | excerpt = models.TextField(blank=True, null=True) 93 | content = models.TextField() 94 | pubdate = models.DateTimeField(db_index=True) 95 | language = models.CharField(max_length=5, choices=settings.LANGUAGES, 96 | blank=True, default=settings.LANGUAGE_CODE) 97 | image = models.CharField(max_length=255, blank=True, null=True) 98 | 99 | tags = TaggableManager(through=I18NTaggedItem) 100 | 101 | def __unicode__(self): 102 | return self.title 103 | 104 | @i18n_permalink 105 | def get_absolute_url(self): 106 | from .utils import get_post_urlpattern_keys 107 | 108 | keys = get_post_urlpattern_keys() 109 | 110 | kwargs = {k: getattr(self, k) for k in keys} 111 | 112 | return ('blog_post', (), kwargs) 113 | 114 | def get_next(self): 115 | return self.get_next_by_pubdate(language=self.language, 116 | is_published=True) 117 | 118 | def get_previous(self): 119 | return self.get_previous_by_pubdate(language=self.language, 120 | is_published=True) 121 | 122 | @property 123 | def tags_list(self): 124 | return self.tags.values_list('name', flat=True) 125 | 126 | @property 127 | def year(self): 128 | return self.pubdate.year 129 | 130 | @property 131 | def month(self): 132 | """Get a 2 digit formatted month""" 133 | return self.pubdate.strftime('%m') 134 | 135 | @property 136 | def day(self): 137 | """Get a 2 digit formatted day""" 138 | return self.pubdate.strftime('%d') 139 | -------------------------------------------------------------------------------- /statirator/core/templatetags/st_core.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from django.template.base import Node, Library, TemplateSyntaxError, kwarg_re 3 | from django.utils.encoding import smart_str 4 | from django.conf import settings 5 | 6 | register = Library() 7 | 8 | 9 | class I18NURLNode(Node): 10 | "Copy of django's url node with a bit of i18n" 11 | 12 | def __init__(self, language, view_name, args, kwargs, asvar, legacy_view_name=True): 13 | self.language = language 14 | self.view_name = view_name 15 | self.legacy_view_name = legacy_view_name 16 | self.args = args 17 | self.kwargs = kwargs 18 | self.asvar = asvar 19 | 20 | def render(self, context): 21 | from django.core.urlresolvers import NoReverseMatch 22 | from statirator.core.utils import i18n_reverse 23 | 24 | args = [arg.resolve(context) for arg in self.args] 25 | kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) 26 | for k, v in self.kwargs.items()]) 27 | 28 | language = self.language.resolve(context) 29 | 30 | view_name = self.view_name 31 | if not self.legacy_view_name: 32 | view_name = view_name.resolve(context) 33 | 34 | # Try to look up the URL twice: once given the view name, and again 35 | # relative to what we guess is the "main" app. If they both fail, 36 | # re-raise the NoReverseMatch unless we're using the 37 | # {% url ... as var %} construct in which cause return nothing. 38 | url = '' 39 | try: 40 | url = i18n_reverse(language, view_name, args=args, kwargs=kwargs, 41 | current_app=context.current_app) 42 | except NoReverseMatch, e: 43 | if settings.SETTINGS_MODULE: 44 | project_name = settings.SETTINGS_MODULE.split('.')[0] 45 | try: 46 | url = i18n_reverse( 47 | language, project_name + '.' + view_name, 48 | args=args, kwargs=kwargs, 49 | current_app=context.current_app) 50 | except NoReverseMatch: 51 | if self.asvar is None: 52 | # Re-raise the original exception, not the one with 53 | # the path relative to the project. This makes a 54 | # better error message. 55 | raise e 56 | else: 57 | if self.asvar is None: 58 | raise e 59 | 60 | if self.asvar: 61 | context[self.asvar] = url 62 | return '' 63 | else: 64 | return url 65 | 66 | 67 | @register.tag 68 | def i18n_url(parser, token): 69 | """ 70 | Returns an absolute URL matching given view with its parameters. 71 | 72 | This is a way to define links that aren't tied to a particular URL 73 | configuration:: 74 | 75 | {% i18n_url language path.to.some_view arg1 arg2 %} 76 | 77 | or 78 | 79 | {% i18n_url language path.to.some_view name1=value1 name2=value2 %} 80 | 81 | The first argument is the language code. 82 | 83 | The second argument is a path to a view. It can be an absolute python path 84 | or just ``app_name.view_name`` without the project name if the view is 85 | located inside the project. Other arguments are comma-separated values 86 | that will be filled in place of positional and keyword arguments in the 87 | URL. All arguments for the URL should be present. 88 | 89 | For example if you have a view ``app_name.client`` taking client's id and 90 | the corresponding line in a URLconf looks like this:: 91 | 92 | ('^client/(\d+)/$', 'app_name.client') 93 | 94 | and this app's URLconf is included into the project's URLconf under some 95 | path:: 96 | 97 | ('^clients/', include('project_name.app_name.urls')) 98 | 99 | then in a template you can create a link for a certain client like this:: 100 | 101 | {% url app_name.client client.id %} 102 | 103 | The URL will look like ``/clients/client/123/``. 104 | """ 105 | 106 | import warnings 107 | warnings.warn('The syntax for the url template tag is changing. Load the `url` tag from the `future` tag library to start using the new behavior.', 108 | category=DeprecationWarning) 109 | 110 | bits = token.split_contents() 111 | if len(bits) < 3: 112 | raise TemplateSyntaxError("'%s' takes at least two arguments" 113 | " (language) (path to a view)" % bits[0]) 114 | 115 | language = parser.compile_filter(bits[1]) 116 | viewname = bits[2] 117 | args = [] 118 | kwargs = {} 119 | asvar = None 120 | bits = bits[3:] 121 | if len(bits) >= 3 and bits[-2] == 'as': 122 | asvar = bits[-1] 123 | bits = bits[:-2] 124 | 125 | # Now all the bits are parsed into new format, 126 | # process them as template vars 127 | if len(bits): 128 | for bit in bits: 129 | match = kwarg_re.match(bit) 130 | if not match: 131 | raise TemplateSyntaxError("Malformed arguments to i18n_url tag") 132 | name, value = match.groups() 133 | if name: 134 | kwargs[name] = parser.compile_filter(value) 135 | else: 136 | args.append(parser.compile_filter(value)) 137 | 138 | return I18NURLNode(language, viewname, args, kwargs, asvar, 139 | legacy_view_name=True) 140 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Statirator.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Statirator.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Statirator.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Statirator.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Statirator" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Statirator" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /statirator/project_template/static/css/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 Boilerplate 3 | * 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html, 14 | button, 15 | input, 16 | select, 17 | textarea { 18 | color: #222; 19 | } 20 | 21 | body { 22 | font-size: 1em; 23 | line-height: 1.4; 24 | } 25 | 26 | /* 27 | * Remove text-shadow in selection highlight: h5bp.com/i 28 | * These selection declarations have to be separate. 29 | * Customize the background color to match your design. 30 | */ 31 | 32 | ::-moz-selection { 33 | background: #b3d4fc; 34 | text-shadow: none; 35 | } 36 | 37 | ::selection { 38 | background: #b3d4fc; 39 | text-shadow: none; 40 | } 41 | 42 | /* 43 | * A better looking default horizontal rule 44 | */ 45 | 46 | hr { 47 | display: block; 48 | height: 1px; 49 | border: 0; 50 | border-top: 1px solid #ccc; 51 | margin: 1em 0; 52 | padding: 0; 53 | } 54 | 55 | /* 56 | * Remove the gap between images and the bottom of their containers: h5bp.com/i/440 57 | */ 58 | 59 | img { 60 | vertical-align: middle; 61 | } 62 | 63 | /* 64 | * Remove default fieldset styles. 65 | */ 66 | 67 | fieldset { 68 | border: 0; 69 | margin: 0; 70 | padding: 0; 71 | } 72 | 73 | /* 74 | * Allow only vertical resizing of textareas. 75 | */ 76 | 77 | textarea { 78 | resize: vertical; 79 | } 80 | 81 | /* ========================================================================== 82 | Chrome Frame prompt 83 | ========================================================================== */ 84 | 85 | .chromeframe { 86 | margin: 0.2em 0; 87 | background: #ccc; 88 | color: #000; 89 | padding: 0.2em 0; 90 | } 91 | 92 | /* ========================================================================== 93 | Author's custom styles 94 | ========================================================================== */ 95 | 96 | .post-prev-next li {display:inline-block} 97 | 98 | .post-prev:before {content: "\2190\00a0"} 99 | .post-next:after {content: "\00a0\2192"} 100 | 101 | html[dir="rtl"] .post-prev:before {content: "\2192\00a0"} 102 | html[dir="rtl"] .post-next:after {content: "\00a0\2190"} 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | /* ========================================================================== 116 | Helper classes 117 | ========================================================================== */ 118 | 119 | /* 120 | * Image replacement 121 | */ 122 | 123 | .ir { 124 | background-color: transparent; 125 | border: 0; 126 | overflow: hidden; 127 | /* IE 6/7 fallback */ 128 | *text-indent: -9999px; 129 | } 130 | 131 | .ir:before { 132 | content: ""; 133 | display: block; 134 | width: 0; 135 | height: 100%; 136 | } 137 | 138 | /* 139 | * Hide from both screenreaders and browsers: h5bp.com/u 140 | */ 141 | 142 | .hidden { 143 | display: none !important; 144 | visibility: hidden; 145 | } 146 | 147 | /* 148 | * Hide only visually, but have it available for screenreaders: h5bp.com/v 149 | */ 150 | 151 | .visuallyhidden { 152 | border: 0; 153 | clip: rect(0 0 0 0); 154 | height: 1px; 155 | margin: -1px; 156 | overflow: hidden; 157 | padding: 0; 158 | position: absolute; 159 | width: 1px; 160 | } 161 | 162 | /* 163 | * Extends the .visuallyhidden class to allow the element to be focusable 164 | * when navigated to via the keyboard: h5bp.com/p 165 | */ 166 | 167 | .visuallyhidden.focusable:active, 168 | .visuallyhidden.focusable:focus { 169 | clip: auto; 170 | height: auto; 171 | margin: 0; 172 | overflow: visible; 173 | position: static; 174 | width: auto; 175 | } 176 | 177 | /* 178 | * Hide visually and from screenreaders, but maintain layout 179 | */ 180 | 181 | .invisible { 182 | visibility: hidden; 183 | } 184 | 185 | /* 186 | * Clearfix: contain floats 187 | * 188 | * For modern browsers 189 | * 1. The space content is one way to avoid an Opera bug when the 190 | * `contenteditable` attribute is included anywhere else in the document. 191 | * Otherwise it causes space to appear at the top and bottom of elements 192 | * that receive the `clearfix` class. 193 | * 2. The use of `table` rather than `block` is only necessary if using 194 | * `:before` to contain the top-margins of child elements. 195 | */ 196 | 197 | .clearfix:before, 198 | .clearfix:after { 199 | content: " "; /* 1 */ 200 | display: table; /* 2 */ 201 | } 202 | 203 | .clearfix:after { 204 | clear: both; 205 | } 206 | 207 | /* 208 | * For IE 6/7 only 209 | * Include this rule to trigger hasLayout and contain floats. 210 | */ 211 | 212 | .clearfix { 213 | *zoom: 1; 214 | } 215 | 216 | /* ========================================================================== 217 | EXAMPLE Media Queries for Responsive Design. 218 | Theses examples override the primary ('mobile first') styles. 219 | Modify as content requires. 220 | ========================================================================== */ 221 | 222 | @media only screen and (min-width: 35em) { 223 | /* Style adjustments for viewports that meet the condition */ 224 | } 225 | 226 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 227 | only screen and (min-resolution: 144dpi) { 228 | /* Style adjustments for high resolution devices */ 229 | } 230 | 231 | /* ========================================================================== 232 | Print styles. 233 | Inlined to avoid required HTTP connection: h5bp.com/r 234 | ========================================================================== */ 235 | 236 | @media print { 237 | * { 238 | background: transparent !important; 239 | color: #000 !important; /* Black prints faster: h5bp.com/s */ 240 | box-shadow:none !important; 241 | text-shadow: none !important; 242 | } 243 | 244 | a, 245 | a:visited { 246 | text-decoration: underline; 247 | } 248 | 249 | a[href]:after { 250 | content: " (" attr(href) ")"; 251 | } 252 | 253 | abbr[title]:after { 254 | content: " (" attr(title) ")"; 255 | } 256 | 257 | /* 258 | * Don't show links for images, or javascript/internal links 259 | */ 260 | 261 | .ir a:after, 262 | a[href^="javascript:"]:after, 263 | a[href^="#"]:after { 264 | content: ""; 265 | } 266 | 267 | pre, 268 | blockquote { 269 | border: 1px solid #999; 270 | page-break-inside: avoid; 271 | } 272 | 273 | thead { 274 | display: table-header-group; /* h5bp.com/t */ 275 | } 276 | 277 | tr, 278 | img { 279 | page-break-inside: avoid; 280 | } 281 | 282 | img { 283 | max-width: 100% !important; 284 | } 285 | 286 | @page { 287 | margin: 0.5cm; 288 | } 289 | 290 | p, 291 | h2, 292 | h3 { 293 | orphans: 3; 294 | widows: 3; 295 | } 296 | 297 | h2, 298 | h3 { 299 | page-break-after: avoid; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Quick Start 3 | =============== 4 | 5 | Installation 6 | ================ 7 | 8 | TODO 9 | 10 | 11 | Initialize The Site 12 | =================== 13 | 14 | Once installed, use the ``statirator init`` command to initialize the site. The 15 | command reference:: 16 | 17 | Usage: statirator init [options] [directory] 18 | 19 | Init the static site project 20 | 21 | Options: 22 | -t TITLE, --title=TITLE 23 | Site title [Default: "Default site"] 24 | -d DOMAIN, --domain=DOMAIN 25 | Domain name [Default: "example.com"] 26 | -l LANGUAGES, --languages=LANGUAGES 27 | Supported languages. [Default: "he,en"] 28 | -z TIMEZONE, --timezone=TIMEZONE 29 | Time Zone. [Default: "America/Chicago"] 30 | 31 | Let's init the site:: 32 | 33 | statirator init example.com 34 | 35 | This will create ``example.com`` directory and the default site skeleton based 36 | on `html5 boilerplate`_: 37 | 38 | .. code-block:: sh 39 | 40 | $ tree example.com/ 41 | example.com/ 42 | |-- blog 43 | | `-- README 44 | |-- conf 45 | | |-- __init__.py 46 | | |-- settings.py 47 | | `-- urls.py 48 | |-- locale 49 | | `-- README 50 | |-- manage.py 51 | |-- pages 52 | | `-- index.html 53 | |-- static 54 | | |-- crossdomain.xml 55 | | |-- css 56 | | | |-- main.css 57 | | | |-- normalize.css 58 | | | `-- normalize_rtl.css 59 | | |-- favicon.ico 60 | | |-- humans.txt 61 | | |-- img 62 | | | |-- apple-touch-icon-114x114-precomposed.png 63 | | | |-- apple-touch-icon-144x144-precomposed.png 64 | | | |-- apple-touch-icon-57x57-precomposed.png 65 | | | |-- apple-touch-icon-72x72-precomposed.png 66 | | | |-- apple-touch-icon.png 67 | | | `-- apple-touch-icon-precomposed.png 68 | | |-- js 69 | | | |-- main.js 70 | | | |-- plugins.js 71 | | | `-- vendor 72 | | | |-- jquery-1.8.0.min.js 73 | | | `-- modernizr-2.6.1.min.js 74 | | `-- robots.txt 75 | `-- templates 76 | `-- README 77 | 78 | 10 directories, 25 files 79 | 80 | .. _html5 boilerplate: http://html5boilerplate.com/ 81 | 82 | 83 | Notable directories and files: 84 | 85 | * ``blog``: posts location 86 | * ``conf``: The django project's settings and url patterns. 87 | * ``manage.py``: Django's manage.py_. Will be used from now on. 88 | * ``pages``: Site's pages (non blog posts, e,g: ``about us``). 89 | * ``static``: Static media files. The files under that directory will be copied 90 | as is. 91 | * ``templates``: Used override the default templates by placing them here. 92 | 93 | .. _manage.py: https://docs.djangoproject.com/en/1.4/ref/django-admin/ 94 | 95 | 96 | Create a Blog Post 97 | ==================== 98 | 99 | Use the ``create_post`` management command. reference:: 100 | 101 | Usage: ./manage.py create_post [options] 102 | 103 | Create a new rst blog post 104 | 105 | Options: 106 | -d, --draft Is is a draft (unpublished) ? [Default: "False"] 107 | 108 | So for example:: 109 | 110 | $ ./manage.py create_post "Welcome to my blog" 111 | Created post blog/welcome-to-my-blog.rst 112 | 113 | Will create a stub for that blog post: 114 | 115 | .. code-block:: sh 116 | 117 | 118 | $ ls -1 blog/ 119 | README 120 | welcome-to-my-blog.rst 121 | 122 | 123 | Default Post Structure 124 | =========================== 125 | 126 | Here's the content of the post: 127 | 128 | .. code-block:: rst 129 | 130 | 131 | :slug: welcome-to-my-blog 132 | :draft: 0 133 | :datetime: 2012-09-22 19:16:45 134 | 135 | .. -- 136 | 137 | ============================================================= 138 | Welcome to my blog 139 | ============================================================= 140 | 141 | :lang: en 142 | :tags: Tag 1|tag-1, Tag 2|tag-2 143 | 144 | English content goes here 145 | 146 | .. -- 147 | 148 | ============================================================= 149 | כותרת עברית 150 | ============================================================= 151 | 152 | :lang: he 153 | :tags: תג 1|tag-1, תג 2|tag-2 154 | 155 | תוכן עברית יבוא כאן 156 | 157 | 158 | This is valid reStructuredText document. The content sections are separated with 159 | ``.. --`` (which is interpreted as comment by reStructuredText). Metadata is 160 | specified with fields_. 161 | 162 | .. _fields: http://docutils.sourceforge.net/docs/user/rst/quickref.html#field-lists 163 | 164 | 165 | The 1st section is generic metadata for the post. 166 | 167 | Following sections are one per language (``lang`` is mandatory). As you can see, 168 | the tags are comma separated and each specifies a tag name and it's slug, 169 | separated by ``|``. After the metadata for each language comes the content. 170 | 171 | 172 | Generate the Static Site 173 | =========================== 174 | 175 | To generate the static site run the ``generate`` command. The will create the 176 | static site in the ``BUILD_DIR`` directory (default: ``build``). Example run: 177 | 178 | .. code-block:: sh 179 | 180 | 181 | [example.com]$ ./manage.py generate 182 | 183 | Syncing in memory db 184 | -------------------- 185 | Creating tables ... 186 | Creating table django_content_type 187 | Creating table django_site 188 | Creating table taggit_tag 189 | Creating table taggit_taggeditem 190 | Creating table blog_i18ntag 191 | Creating table blog_i18ntaggeditem 192 | Creating table blog_post 193 | Creating table pages_page 194 | Installing custom SQL ... 195 | Installing indexes ... 196 | 197 | Reading resource 198 | ---------------- 199 | Processing /home/meir/devel/Projects/meirkriheli/example.com/blog/welcome-to-my-blog.rst 200 | Processing /home/meir/devel/Projects/meirkriheli/example.com/pages/index.html 201 | 202 | Generating static pages 203 | ----------------------- 204 | Skipping app 'conf'... (No 'renderers.py') 205 | Skipping app 'django.contrib.contenttypes'... (No 'renderers.py') 206 | Skipping app 'django.contrib.sites'... (No 'renderers.py') 207 | Skipping app 'django.contrib.staticfiles'... (No 'renderers.py') 208 | Skipping app 'taggit'... (No 'renderers.py') 209 | Skipping app 'disqus'... (No 'renderers.py') 210 | Skipping app 'statirator.core'... (No 'renderers.py') 211 | Found renderers for 'statirator.blog'... 212 | Found renderers for 'statirator.pages'... 213 | 214 | example.com/build/en/2012/09/welcome-to-my-blog/index.html 215 | example.com/build/en/archive/index.html 216 | example.com/build/en/blog.rss 217 | example.com/build/2012/09/welcome-to-my-blog/index.html 218 | example.com/build/archive/index.html 219 | example.com/build/blog.rss 220 | example.com/build/en/tags/tag-1/index.html 221 | example.com/build/en/tags/tag-2/index.html 222 | example.com/build/en/tags/tag-1/tag.rss 223 | example.com/build/en/tags/tag-2/tag.rss 224 | example.com/build/en/tags/index.html 225 | example.com/build/tags/tag-1/index.html 226 | example.com/build/tags/tag-2/index.html 227 | example.com/build/tags/tag-1/tag.rss 228 | example.com/build/tags/tag-2/tag.rss 229 | example.com/build/tags/index.html 230 | example.com/build/en/index.html 231 | example.com/build/index.html 232 | 233 | Collecting static media 234 | ----------------------- 235 | example.com/static/crossdomain.xml' 236 | example.com/static/humans.txt' 237 | example.com/static/robots.txt' 238 | example.com/static/favicon.ico' 239 | example.com/static/img/apple-touch-icon-precomposed.png' 240 | example.com/static/img/apple-touch-icon-114x114-precomposed.png' 241 | example.com/static/img/apple-touch-icon-57x57-precomposed.png' 242 | example.com/static/img/apple-touch-icon.png' 243 | example.com/static/img/apple-touch-icon-144x144-precomposed.png' 244 | example.com/static/img/apple-touch-icon-72x72-precomposed.png' 245 | example.com/static/js/main.js' 246 | example.com/static/js/plugins.js' 247 | example.com/static/js/vendor/jquery-1.8.0.min.js' 248 | example.com/static/js/vendor/modernizr-2.6.1.min.js' 249 | example.com/static/css/normalize.css' 250 | example.com/static/css/main.css' 251 | example.com/static/css/normalize_rtl.css' 252 | 253 | 17 static files copied. 254 | 255 | 256 | Serving the static site 257 | ========================== 258 | 259 | Run the command:: 260 | 261 | ./manage.py serve 262 | 263 | To run and auto regenrate, run:: 264 | 265 | ./manage.py serve -g 266 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Statirator documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Sep 23 01:34:44 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Statirator' 44 | copyright = u'2012, Meir Kriheli' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.2.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.2.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'Statiratordoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'Statirator.tex', u'Statirator Documentation', 187 | u'Meir Kriheli', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'statirator', u'Statirator Documentation', 217 | [u'Meir Kriheli'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'Statirator', u'Statirator Documentation', 231 | u'Meir Kriheli', 'Statirator', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /statirator/core/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os 4 | import sys 5 | 6 | import functools 7 | import logging 8 | import urlparse 9 | from BeautifulSoup import BeautifulSoup, Tag 10 | from django.conf import settings 11 | 12 | 13 | def content_absolute_links(content, image=None): 14 | from django.contrib.sites.models import Site 15 | current_site = Site.objects.get(pk=settings.SITE_ID) 16 | 17 | def abs_url(url): 18 | 19 | parsed = urlparse.urlparse(url) 20 | if parsed.netloc == parsed.scheme == '': 21 | url = urlparse.urljoin('http://{0}'.format(current_site.domain), url) 22 | return url 23 | 24 | soup = BeautifulSoup(content) 25 | 26 | if image: 27 | img = Tag(soup, 'img', [('src', image)]) 28 | soup.insert(0, img) 29 | 30 | for link in soup.findAll('a'): 31 | link['href'] = abs_url(link['href']) 32 | 33 | for link in soup.findAll('img'): 34 | link['src'] = abs_url(link['src']) 35 | 36 | return unicode(soup) 37 | 38 | 39 | LANGS_DICT = dict(settings.LANGUAGES) 40 | 41 | 42 | def find_readers(): 43 | """Auto discover readers in installed apps. 44 | 45 | Each readers.py should have the READERS list with reader classes 46 | """ 47 | 48 | from django.conf import settings 49 | from django.utils.importlib import import_module 50 | from django.utils.module_loading import module_has_submodule 51 | 52 | readers = [] 53 | for app in settings.INSTALLED_APPS: 54 | mod = import_module(app) 55 | # Attempt to import the app's admin module. 56 | try: 57 | walk_mod = import_module('%s.readers' % app) 58 | if hasattr(walk_mod, 'READERS'): 59 | readers.extend(walk_mod.READERS) 60 | else: 61 | logging.warn( 62 | 'readers found in app %s, but has no READERS attrib', 63 | app) 64 | except: 65 | # Decide whether to bubble up this error. If the app just 66 | # doesn't have an readers module, we can ignore the error 67 | # attempting to import it, otherwise we want it to bubble up. 68 | if module_has_submodule(mod, 'readers'): 69 | raise 70 | 71 | return readers 72 | 73 | 74 | def find_files(root, extensions): 75 | """Generator finding files from a root dir with specific extensions 76 | 77 | :param root: Root directory for the search 78 | :param extensions: List of extensions to search (e.g: ['.rst', '.txt']) 79 | """ 80 | 81 | for root, dirs, files in os.walk(root): 82 | for f in files: 83 | if os.path.splitext(f)[1] in extensions: 84 | yield os.path.join(root, f) 85 | 86 | 87 | def i18n_permalink(*args): 88 | """Return the correct URL in case of not the default language. 89 | relaces djangos models.permalink. 90 | 91 | Assumes the name of the i18n version of the url pattern is prefix with 92 | 'i18n_' (This is done in the statirator's default urls.py). 93 | 94 | It looks for a `language` field for the instance. If the name is different, 95 | pass it as the first param to the decorator. e.g:: 96 | 97 | @i18n_permalink('lang_code') 98 | def get_absolute_url(self): 99 | return (...) 100 | 101 | """ 102 | def outer(f): 103 | @functools.wraps(f) 104 | def wrapper(obj, *args, **kwargs): 105 | 106 | bits = f(obj, *args, **kwargs) 107 | name = bits[0] 108 | 109 | lang = getattr(obj, language_field) 110 | return i18n_reverse(lang, name, None, *bits[1:3]) 111 | 112 | return wrapper 113 | 114 | if len(args) == 1 and callable(args[0]): 115 | # No arguments, this is the decorator 116 | # Set default values for the arguments 117 | language_field = 'language' 118 | return outer(args[0]) 119 | else: 120 | language_field = args[0] 121 | return outer 122 | 123 | 124 | def i18n_reverse(language, viewname, *args, **kwargs): 125 | """Django's reverse with a pinch of i18n. 126 | 127 | Assumes the name of the i18n version of the url pattern is prefix with 128 | 'i18n_' (This is done in the statirator's default urls.py). 129 | 130 | """ 131 | from django.core.urlresolvers import reverse 132 | from django.conf import settings 133 | from django.utils.translation import activate, get_language 134 | 135 | cur_lang = get_language() 136 | 137 | # activate the obj's lang for correct i18n_patterns reverse 138 | activate(language) 139 | if language != settings.LANGUAGE_CODE: 140 | viewname = 'i18n_' + viewname 141 | 142 | res = reverse(viewname, *args, **kwargs) 143 | activate(cur_lang) 144 | 145 | return res 146 | 147 | 148 | def path_to_lang(path, lang): 149 | "Translate one path to another languge, takes into account " 150 | 151 | prefix = postfix = '' 152 | 153 | if path.startswith('/'): 154 | path = path[1:] 155 | prefix = '/' 156 | if path.endswith('/'): 157 | path = path[:-1] 158 | postfix = '/' 159 | 160 | bits = path.split('/') 161 | 162 | #TODO fix for multi domain or prefixed default language 163 | if bits[0] in LANGS_DICT: 164 | bits.pop(0) 165 | else: # assume it's the default language. 166 | pass 167 | 168 | if lang != settings.LANGUAGE_CODE: 169 | bits.insert(0, lang) 170 | 171 | return prefix + '/'.join(bits) + postfix 172 | 173 | 174 | # The following allow rendering specific template blocks (for data extraction) 175 | # Taken from: http://djangosnippets.org/snippets/942/ 176 | 177 | def get_template(template): 178 | from django.template import loader 179 | 180 | if isinstance(template, (tuple, list)): 181 | return loader.select_template(template) 182 | return loader.get_template(template) 183 | 184 | 185 | class BlockNotFound(Exception): 186 | pass 187 | 188 | 189 | def render_template_block(template, block, context): 190 | """ 191 | Renders a single block from a template. This template should have previously been rendered. 192 | """ 193 | template._render(context) 194 | return render_template_block_nodelist(template.nodelist, block, context) 195 | 196 | 197 | def render_template_block_nodelist(nodelist, block, context): 198 | from django.template.loader_tags import BlockNode, ExtendsNode 199 | 200 | for node in nodelist: 201 | if isinstance(node, BlockNode) and node.name == block: 202 | return node.render(context) 203 | for key in ('nodelist', 'nodelist_true', 'nodelist_false'): 204 | if hasattr(node, key): 205 | try: 206 | return render_template_block_nodelist(getattr(node, key), block, context) 207 | except: 208 | pass 209 | for node in nodelist: 210 | if isinstance(node, ExtendsNode): 211 | try: 212 | return render_template_block(node.get_parent(context), block, context) 213 | except BlockNotFound: 214 | pass 215 | raise BlockNotFound 216 | 217 | 218 | def render_block_to_string(template_or_name, block, dictionary=None, 219 | context_instance=None): 220 | """ 221 | Loads the given template_name and renders the given block with the given dictionary as 222 | context. Returns a string. 223 | 224 | Can be used to extract data from template blocks (e.g: get the title from 225 | {% block title %}{% endblock %}) 226 | 227 | """ 228 | from django.template import Context, Template 229 | 230 | dictionary = dictionary or {} 231 | if isinstance(template_or_name, Template): 232 | t = template_or_name 233 | else: 234 | t = get_template(template_or_name) 235 | 236 | if context_instance: 237 | context_instance.update(dictionary) 238 | else: 239 | context_instance = Context(dictionary) 240 | return render_template_block(t, block, context_instance) 241 | 242 | 243 | _mtimes = {} 244 | _win = (sys.platform == "win32") 245 | 246 | 247 | def filesystem_changed(root_dir, ignore_dirs=None, ignore_re=None): 248 | "Do we have new, changed, or deleted files under ``root_dir``" 249 | 250 | global _mtimes, _win 251 | 252 | found = {} 253 | 254 | if ignore_dirs is None: 255 | ignore_dirs = [] 256 | 257 | for root, dirs, files in os.walk(root_dir): 258 | dirs[:] = [x for x in dirs 259 | if x[0] != '.' and 260 | os.path.abspath(os.path.join(root, x)) not in ignore_dirs] 261 | 262 | for resource in files: 263 | full = os.path.join(root, resource) 264 | if ignore_re and ignore_re.match(full): 265 | continue 266 | 267 | stat = os.stat(full) 268 | mtime = stat.st_mtime 269 | if _win: 270 | mtime -= stat.st_ctime 271 | 272 | found[full] = int(mtime) 273 | 274 | if found != _mtimes: 275 | _mtimes = found.copy() 276 | return True 277 | 278 | return False 279 | -------------------------------------------------------------------------------- /statirator/project_template/static/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v1.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | *display: inline; 34 | *zoom: 1; 35 | } 36 | 37 | /* 38 | * Prevents modern browsers from displaying `audio` without controls. 39 | * Remove excess height in iOS 5 devices. 40 | */ 41 | 42 | audio:not([controls]) { 43 | display: none; 44 | height: 0; 45 | } 46 | 47 | /* 48 | * Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3, 49 | * and Safari 4. 50 | * Known issue: no IE 6 support. 51 | */ 52 | 53 | [hidden] { 54 | display: none; 55 | } 56 | 57 | /* ========================================================================== 58 | Base 59 | ========================================================================== */ 60 | 61 | /* 62 | * 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using 63 | * `em` units. 64 | * 2. Prevents iOS text size adjust after orientation change, without disabling 65 | * user zoom. 66 | */ 67 | 68 | html { 69 | font-size: 100%; /* 1 */ 70 | -webkit-text-size-adjust: 100%; /* 2 */ 71 | -ms-text-size-adjust: 100%; /* 2 */ 72 | } 73 | 74 | /* 75 | * Addresses `font-family` inconsistency between `textarea` and other form 76 | * elements. 77 | */ 78 | 79 | html, 80 | button, 81 | input, 82 | select, 83 | textarea { 84 | font-family: sans-serif; 85 | } 86 | 87 | /* 88 | * Addresses margins handled incorrectly in IE 6/7. 89 | */ 90 | 91 | body { 92 | margin: 0; 93 | } 94 | 95 | /* ========================================================================== 96 | Links 97 | ========================================================================== */ 98 | 99 | /* 100 | * Addresses `outline` inconsistency between Chrome and other browsers. 101 | */ 102 | 103 | a:focus { 104 | outline: thin dotted; 105 | } 106 | 107 | /* 108 | * Improves readability when focused and also mouse hovered in all browsers. 109 | */ 110 | 111 | a:active, 112 | a:hover { 113 | outline: 0; 114 | } 115 | 116 | /* ========================================================================== 117 | Typography 118 | ========================================================================== */ 119 | 120 | /* 121 | * Addresses font sizes and margins set differently in IE 6/7. 122 | * Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5, 123 | * and Chrome. 124 | */ 125 | 126 | h1 { 127 | font-size: 2em; 128 | margin: 0.67em 0; 129 | } 130 | 131 | h2 { 132 | font-size: 1.5em; 133 | margin: 0.83em 0; 134 | } 135 | 136 | h3 { 137 | font-size: 1.17em; 138 | margin: 1em 0; 139 | } 140 | 141 | h4 { 142 | font-size: 1em; 143 | margin: 1.33em 0; 144 | } 145 | 146 | h5 { 147 | font-size: 0.83em; 148 | margin: 1.67em 0; 149 | } 150 | 151 | h6 { 152 | font-size: 0.75em; 153 | margin: 2.33em 0; 154 | } 155 | 156 | /* 157 | * Addresses styling not present in IE 7/8/9, Safari 5, and Chrome. 158 | */ 159 | 160 | abbr[title] { 161 | border-bottom: 1px dotted; 162 | } 163 | 164 | /* 165 | * Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. 166 | */ 167 | 168 | b, 169 | strong { 170 | font-weight: bold; 171 | } 172 | 173 | blockquote { 174 | margin: 1em 40px; 175 | } 176 | 177 | /* 178 | * Addresses styling not present in Safari 5 and Chrome. 179 | */ 180 | 181 | dfn { 182 | font-style: italic; 183 | } 184 | 185 | /* 186 | * Addresses styling not present in IE 6/7/8/9. 187 | */ 188 | 189 | mark { 190 | background: #ff0; 191 | color: #000; 192 | } 193 | 194 | /* 195 | * Addresses margins set differently in IE 6/7. 196 | */ 197 | 198 | p, 199 | pre { 200 | margin: 1em 0; 201 | } 202 | 203 | /* 204 | * Corrects font family set oddly in IE 6, Safari 4/5, and Chrome. 205 | */ 206 | 207 | code, 208 | kbd, 209 | pre, 210 | samp { 211 | font-family: monospace, serif; 212 | _font-family: 'courier new', monospace; 213 | font-size: 1em; 214 | } 215 | 216 | /* 217 | * Improves readability of pre-formatted text in all browsers. 218 | */ 219 | 220 | pre { 221 | white-space: pre; 222 | white-space: pre-wrap; 223 | word-wrap: break-word; 224 | } 225 | 226 | /* 227 | * Addresses CSS quotes not supported in IE 6/7. 228 | */ 229 | 230 | q { 231 | quotes: none; 232 | } 233 | 234 | /* 235 | * Addresses `quotes` property not supported in Safari 4. 236 | */ 237 | 238 | q:before, 239 | q:after { 240 | content: ''; 241 | content: none; 242 | } 243 | 244 | /* 245 | * Addresses inconsistent and variable font size in all browsers. 246 | */ 247 | 248 | small { 249 | font-size: 80%; 250 | } 251 | 252 | /* 253 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 254 | */ 255 | 256 | sub, 257 | sup { 258 | font-size: 75%; 259 | line-height: 0; 260 | position: relative; 261 | vertical-align: baseline; 262 | } 263 | 264 | sup { 265 | top: -0.5em; 266 | } 267 | 268 | sub { 269 | bottom: -0.25em; 270 | } 271 | 272 | /* ========================================================================== 273 | Lists 274 | ========================================================================== */ 275 | 276 | /* 277 | * Addresses margins set differently in IE 6/7. 278 | */ 279 | 280 | dl, 281 | menu, 282 | ol, 283 | ul { 284 | margin: 1em 0; 285 | } 286 | 287 | dd { 288 | margin: 0 0 0 40px; 289 | } 290 | 291 | /* 292 | * Addresses paddings set differently in IE 6/7. 293 | */ 294 | 295 | menu, 296 | ol, 297 | ul { 298 | padding: 0 0 0 40px; 299 | } 300 | 301 | /* 302 | * Corrects list images handled incorrectly in IE 7. 303 | */ 304 | 305 | nav ul, 306 | nav ol { 307 | list-style: none; 308 | list-style-image: none; 309 | } 310 | 311 | /* ========================================================================== 312 | Embedded content 313 | ========================================================================== */ 314 | 315 | /* 316 | * 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3. 317 | * 2. Improves image quality when scaled in IE 7. 318 | */ 319 | 320 | img { 321 | border: 0; /* 1 */ 322 | -ms-interpolation-mode: bicubic; /* 2 */ 323 | } 324 | 325 | /* 326 | * Corrects overflow displayed oddly in IE 9. 327 | */ 328 | 329 | svg:not(:root) { 330 | overflow: hidden; 331 | } 332 | 333 | /* ========================================================================== 334 | Figures 335 | ========================================================================== */ 336 | 337 | /* 338 | * Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11. 339 | */ 340 | 341 | figure { 342 | margin: 0; 343 | } 344 | 345 | /* ========================================================================== 346 | Forms 347 | ========================================================================== */ 348 | 349 | /* 350 | * Corrects margin displayed oddly in IE 6/7. 351 | */ 352 | 353 | form { 354 | margin: 0; 355 | } 356 | 357 | /* 358 | * Define consistent border, margin, and padding. 359 | */ 360 | 361 | fieldset { 362 | border: 1px solid #c0c0c0; 363 | margin: 0 2px; 364 | padding: 0.35em 0.625em 0.75em; 365 | } 366 | 367 | /* 368 | * 1. Corrects color not being inherited in IE 6/7/8/9. 369 | * 2. Corrects text not wrapping in Firefox 3. 370 | * 3. Corrects alignment displayed oddly in IE 6/7. 371 | */ 372 | 373 | legend { 374 | border: 0; /* 1 */ 375 | padding: 0; 376 | white-space: normal; /* 2 */ 377 | *margin-left: -7px; /* 3 */ 378 | } 379 | 380 | /* 381 | * 1. Corrects font size not being inherited in all browsers. 382 | * 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5, 383 | * and Chrome. 384 | * 3. Improves appearance and consistency in all browsers. 385 | */ 386 | 387 | button, 388 | input, 389 | select, 390 | textarea { 391 | font-size: 100%; /* 1 */ 392 | margin: 0; /* 2 */ 393 | vertical-align: baseline; /* 3 */ 394 | *vertical-align: middle; /* 3 */ 395 | } 396 | 397 | /* 398 | * Addresses Firefox 3+ setting `line-height` on `input` using `!important` in 399 | * the UA stylesheet. 400 | */ 401 | 402 | button, 403 | input { 404 | line-height: normal; 405 | } 406 | 407 | /* 408 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 409 | * and `video` controls. 410 | * 2. Corrects inability to style clickable `input` types in iOS. 411 | * 3. Improves usability and consistency of cursor style between image-type 412 | * `input` and others. 413 | * 4. Removes inner spacing in IE 7 without affecting normal text inputs. 414 | * Known issue: inner spacing remains in IE 6. 415 | */ 416 | 417 | button, 418 | html input[type="button"], /* 1 */ 419 | input[type="reset"], 420 | input[type="submit"] { 421 | -webkit-appearance: button; /* 2 */ 422 | cursor: pointer; /* 3 */ 423 | *overflow: visible; /* 4 */ 424 | } 425 | 426 | /* 427 | * Re-set default cursor for disabled elements. 428 | */ 429 | 430 | button[disabled], 431 | input[disabled] { 432 | cursor: default; 433 | } 434 | 435 | /* 436 | * 1. Addresses box sizing set to content-box in IE 8/9. 437 | * 2. Removes excess padding in IE 8/9. 438 | * 3. Removes excess padding in IE 7. 439 | * Known issue: excess padding remains in IE 6. 440 | */ 441 | 442 | input[type="checkbox"], 443 | input[type="radio"] { 444 | box-sizing: border-box; /* 1 */ 445 | padding: 0; /* 2 */ 446 | *height: 13px; /* 3 */ 447 | *width: 13px; /* 3 */ 448 | } 449 | 450 | /* 451 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 452 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 453 | * (include `-moz` to future-proof). 454 | */ 455 | 456 | input[type="search"] { 457 | -webkit-appearance: textfield; /* 1 */ 458 | -moz-box-sizing: content-box; 459 | -webkit-box-sizing: content-box; /* 2 */ 460 | box-sizing: content-box; 461 | } 462 | 463 | /* 464 | * Removes inner padding and search cancel button in Safari 5 and Chrome 465 | * on OS X. 466 | */ 467 | 468 | input[type="search"]::-webkit-search-cancel-button, 469 | input[type="search"]::-webkit-search-decoration { 470 | -webkit-appearance: none; 471 | } 472 | 473 | /* 474 | * Removes inner padding and border in Firefox 3+. 475 | */ 476 | 477 | button::-moz-focus-inner, 478 | input::-moz-focus-inner { 479 | border: 0; 480 | padding: 0; 481 | } 482 | 483 | /* 484 | * 1. Removes default vertical scrollbar in IE 6/7/8/9. 485 | * 2. Improves readability and alignment in all browsers. 486 | */ 487 | 488 | textarea { 489 | overflow: auto; /* 1 */ 490 | vertical-align: top; /* 2 */ 491 | } 492 | 493 | /* ========================================================================== 494 | Tables 495 | ========================================================================== */ 496 | 497 | /* 498 | * Remove most spacing between table cells. 499 | */ 500 | 501 | table { 502 | border-collapse: collapse; 503 | border-spacing: 0; 504 | } 505 | -------------------------------------------------------------------------------- /statirator/templates/html5/modernizr.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.0.6 (Custom Build) | MIT & BSD 2 | * Build: http://www.modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-iepp-respond-mq-cssclasses-teststyles-testprop-testallprops-prefixes-domprefixes-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function E(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1),d=(a+" "+p.join(c+" ")+c).split(" ");return D(d,b)}function D(a,b){for(var d in a)if(k[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function C(a,b){return!!~(""+a).indexOf(b)}function B(a,b){return typeof a===b}function A(a,b){return z(o.join(a+";")+(b||""))}function z(a){k.cssText=a}var d="2.0.6",e={},f=!0,g=b.documentElement,h=b.head||b.getElementsByTagName("head")[0],i="modernizr",j=b.createElement(i),k=j.style,l,m=":)",n=Object.prototype.toString,o=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),p="Webkit Moz O ms Khtml".split(" "),q={},r={},s={},t=[],u=function(a,c,d,e){var f,h,j,k=b.createElement("div");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:i+(d+1),k.appendChild(j);f=["­",""].join(""),k.id=i,k.innerHTML+=f,g.appendChild(k),h=c(k,a),k.parentNode.removeChild(k);return!!h},v=function(b){if(a.matchMedia)return matchMedia(b).matches;var c;u("@media "+b+" { #"+i+" { position: absolute; } }",function(b){c=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).position=="absolute"});return c},w,x={}.hasOwnProperty,y;!B(x,c)&&!B(x.call,c)?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],c)};var F=function(a,c){var d=a.join(""),f=c.length;u(d,function(a,c){var d=b.styleSheets[b.styleSheets.length-1],g=d.cssRules&&d.cssRules[0]?d.cssRules[0].cssText:d.cssText||"",h=a.childNodes,i={};while(f--)i[h[f].id]=h[f];e.csstransforms3d=i.csstransforms3d.offsetLeft===9,e.generatedcontent=i.generatedcontent.offsetHeight>=1,e.fontface=/src/i.test(g)&&g.indexOf(c.split(" ")[0])===0},f,c)}(['@font-face {font-family:"font";src:url("https://")}',["@media (",o.join("transform-3d),("),i,")","{#csstransforms3d{left:9px;position:absolute}}"].join(""),['#generatedcontent:after{content:"',m,'";visibility:hidden}'].join("")],["fontface","csstransforms3d","generatedcontent"]);q.flexbox=function(){function c(a,b,c,d){a.style.cssText=o.join(b+":"+c+";")+(d||"")}function a(a,b,c,d){b+=":",a.style.cssText=(b+o.join(c+";"+b)).slice(0,-b.length)+(d||"")}var d=b.createElement("div"),e=b.createElement("div");a(d,"display","box","width:42px;padding:0;"),c(e,"box-flex","1","width:10px;"),d.appendChild(e),g.appendChild(d);var f=e.offsetWidth===42;d.removeChild(e),g.removeChild(d);return f},q.rgba=function(){z("background-color:rgba(150,255,150,.5)");return C(k.backgroundColor,"rgba")},q.hsla=function(){z("background-color:hsla(120,40%,100%,.5)");return C(k.backgroundColor,"rgba")||C(k.backgroundColor,"hsla")},q.multiplebgs=function(){z("background:url(https://),url(https://),red url(https://)");return/(url\s*\(.*?){3}/.test(k.background)},q.backgroundsize=function(){return E("backgroundSize")},q.borderimage=function(){return E("borderImage")},q.borderradius=function(){return E("borderRadius")},q.boxshadow=function(){return E("boxShadow")},q.textshadow=function(){return b.createElement("div").style.textShadow===""},q.opacity=function(){A("opacity:.55");return/^0.55$/.test(k.opacity)},q.cssanimations=function(){return E("animationName")},q.csscolumns=function(){return E("columnCount")},q.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";z((a+o.join(b+a)+o.join(c+a)).slice(0,-a.length));return C(k.backgroundImage,"gradient")},q.cssreflections=function(){return E("boxReflect")},q.csstransforms=function(){return!!D(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},q.csstransforms3d=function(){var a=!!D(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=e.csstransforms3d);return a},q.csstransitions=function(){return E("transitionProperty")},q.fontface=function(){return e.fontface},q.generatedcontent=function(){return e.generatedcontent};for(var G in q)y(q,G)&&(w=G.toLowerCase(),e[w]=q[G](),t.push((e[w]?"":"no-")+w));z(""),j=l=null,a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="";return a.childNodes.length!==1}()&&function(a,b){function s(a){var b=-1;while(++b=u.minw)&&(!u.maxw||u.maxw&&l<=u.maxw))m[u.media]||(m[u.media]=[]),m[u.media].push(f[u.rules])}for(var t in g)g[t]&&g[t].parentNode===j&&j.removeChild(g[t]);for(var t in m){var v=c.createElement("style"),w=m[t].join("\n");v.type="text/css",v.media=t,v.styleSheet?v.styleSheet.cssText=w:v.appendChild(c.createTextNode(w)),n.appendChild(v),g.push(v)}j.insertBefore(n,o.nextSibling)}},s=function(a,b){var c=t();if(!!c){c.open("GET",a,!0),c.onreadystatechange=function(){c.readyState==4&&(c.status==200||c.status==304)&&b(c.responseText)};if(c.readyState==4)return;c.send()}},t=function(){var a=!1,b=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new XMLHttpRequest}],c=b.length;while(c--){try{a=b[c]()}catch(d){continue}break}return function(){return a}}();m(),respond.update=m,a.addEventListener?a.addEventListener("resize",u,!1):a.attachEvent&&a.attachEvent("onresize",u)}}(this,Modernizr.mq("only all")),function(a,b,c){function k(a){return!a||a=="loaded"||a=="complete"}function j(){var a=1,b=-1;while(p.length- ++b)if(p[b].s&&!(a=p[b].r))break;a&&g()}function i(a){var c=b.createElement("script"),d;c.src=a.s,c.onreadystatechange=c.onload=function(){!d&&k(c.readyState)&&(d=1,j(),c.onload=c.onreadystatechange=null)},m(function(){d||(d=1,j())},H.errorTimeout),a.e?c.onload():n.parentNode.insertBefore(c,n)}function h(a){var c=b.createElement("link"),d;c.href=a.s,c.rel="stylesheet",c.type="text/css";if(!a.e&&(w||r)){var e=function(a){m(function(){if(!d)try{a.sheet.cssRules.length?(d=1,j()):e(a)}catch(b){b.code==1e3||b.message=="security"||b.message=="denied"?(d=1,m(function(){j()},0)):e(a)}},0)};e(c)}else c.onload=function(){d||(d=1,m(function(){j()},0))},a.e&&c.onload();m(function(){d||(d=1,j())},H.errorTimeout),!a.e&&n.parentNode.insertBefore(c,n)}function g(){var a=p.shift();q=1,a?a.t?m(function(){a.t=="c"?h(a):i(a)},0):(a(),j()):q=0}function f(a,c,d,e,f,h){function i(){!o&&k(l.readyState)&&(r.r=o=1,!q&&j(),l.onload=l.onreadystatechange=null,m(function(){u.removeChild(l)},0))}var l=b.createElement(a),o=0,r={t:d,s:c,e:h};l.src=l.data=c,!s&&(l.style.display="none"),l.width=l.height="0",a!="object"&&(l.type=d),l.onload=l.onreadystatechange=i,a=="img"?l.onerror=i:a=="script"&&(l.onerror=function(){r.e=r.r=1,g()}),p.splice(e,0,r),u.insertBefore(l,s?null:n),m(function(){o||(u.removeChild(l),r.r=r.e=o=1,j())},H.errorTimeout)}function e(a,b,c){var d=b=="c"?z:y;q=0,b=b||"j",C(a)?f(d,a,b,this.i++,l,c):(p.splice(this.i++,0,a),p.length==1&&g());return this}function d(){var a=H;a.loader={load:e,i:0};return a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=r&&!s,u=s?l:n.parentNode,v=a.opera&&o.call(a.opera)=="[object Opera]",w="webkitAppearance"in l.style,x=w&&"async"in b.createElement("script"),y=r?"object":v||x?"img":"script",z=w?"img":y,A=Array.isArray||function(a){return o.call(a)=="[object Array]"},B=function(a){return Object(a)===a},C=function(a){return typeof a=="string"},D=function(a){return o.call(a)=="[object Function]"},E=[],F={},G,H;H=function(a){function f(a){var b=a.split("!"),c=E.length,d=b.pop(),e=b.length,f={url:d,origUrl:d,prefixes:b},g,h;for(h=0;h',a,""].join(""),k.id=h,(l?k:m).innerHTML+=f,m.appendChild(k),l||(m.style.background="",g.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(['#modernizr:after{content:"',l,'";visibility:hidden}'].join(""),function(b){a=b.offsetHeight>=1}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f 22 | Header set X-UA-Compatible "IE=Edge,chrome=1" 23 | # mod_headers can't match by content-type, but we don't want to send this header on *everything*... 24 | 25 | Header unset X-UA-Compatible 26 | 27 | 28 | 29 | 30 | # ---------------------------------------------------------------------- 31 | # Cross-domain AJAX requests 32 | # ---------------------------------------------------------------------- 33 | 34 | # Serve cross-domain Ajax requests, disabled by default. 35 | # enable-cors.org 36 | # code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 37 | 38 | # 39 | # Header set Access-Control-Allow-Origin "*" 40 | # 41 | 42 | 43 | # ---------------------------------------------------------------------- 44 | # CORS-enabled images (@crossorigin) 45 | # ---------------------------------------------------------------------- 46 | 47 | # Send CORS headers if browsers request them; enabled by default for images. 48 | # developer.mozilla.org/en/CORS_Enabled_Image 49 | # blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 50 | # hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 51 | # wiki.mozilla.org/Security/Reviews/crossoriginAttribute 52 | 53 | 54 | 55 | # mod_headers, y u no match by Content-Type?! 56 | 57 | SetEnvIf Origin ":" IS_CORS 58 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 59 | 60 | 61 | 62 | 63 | 64 | # ---------------------------------------------------------------------- 65 | # Webfont access 66 | # ---------------------------------------------------------------------- 67 | 68 | # Allow access from all domains for webfonts. 69 | # Alternatively you could only whitelist your 70 | # subdomains like "subdomain.example.com". 71 | 72 | 73 | 74 | Header set Access-Control-Allow-Origin "*" 75 | 76 | 77 | 78 | 79 | # ---------------------------------------------------------------------- 80 | # Proper MIME type for all files 81 | # ---------------------------------------------------------------------- 82 | 83 | # JavaScript 84 | # Normalize to standard type (it's sniffed in IE anyways) 85 | # tools.ietf.org/html/rfc4329#section-7.2 86 | AddType application/javascript js jsonp 87 | AddType application/json json 88 | 89 | # Audio 90 | AddType audio/ogg oga ogg 91 | AddType audio/mp4 m4a f4a f4b 92 | 93 | # Video 94 | AddType video/ogg ogv 95 | AddType video/mp4 mp4 m4v f4v f4p 96 | AddType video/webm webm 97 | AddType video/x-flv flv 98 | 99 | # SVG 100 | # Required for svg webfonts on iPad 101 | # twitter.com/FontSquirrel/status/14855840545 102 | AddType image/svg+xml svg svgz 103 | AddEncoding gzip svgz 104 | 105 | # Webfonts 106 | AddType application/vnd.ms-fontobject eot 107 | AddType application/x-font-ttf ttf ttc 108 | AddType font/opentype otf 109 | AddType application/x-font-woff woff 110 | 111 | # Assorted types 112 | AddType image/x-icon ico 113 | AddType image/webp webp 114 | AddType text/cache-manifest appcache manifest 115 | AddType text/x-component htc 116 | AddType application/xml rss atom xml rdf 117 | AddType application/x-chrome-extension crx 118 | AddType application/x-opera-extension oex 119 | AddType application/x-xpinstall xpi 120 | AddType application/octet-stream safariextz 121 | AddType application/x-web-app-manifest+json webapp 122 | AddType text/x-vcard vcf 123 | AddType application/x-shockwave-flash swf 124 | AddType text/vtt vtt 125 | 126 | 127 | # ---------------------------------------------------------------------- 128 | # Allow concatenation from within specific js and css files 129 | # ---------------------------------------------------------------------- 130 | 131 | # e.g. Inside of script.combined.js you could have 132 | # 133 | # 134 | # and they would be included into this single file. 135 | 136 | # This is not in use in the boilerplate as it stands. You may 137 | # choose to use this technique if you do not have a build process. 138 | 139 | # 140 | # Options +Includes 141 | # AddOutputFilterByType INCLUDES application/javascript application/json 142 | # SetOutputFilter INCLUDES 143 | # 144 | 145 | # 146 | # Options +Includes 147 | # AddOutputFilterByType INCLUDES text/css 148 | # SetOutputFilter INCLUDES 149 | # 150 | 151 | 152 | # ---------------------------------------------------------------------- 153 | # Gzip compression 154 | # ---------------------------------------------------------------------- 155 | 156 | 157 | 158 | # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/ 159 | 160 | 161 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 162 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 163 | 164 | 165 | 166 | # Compress all output labeled with one of the following MIME-types 167 | 168 | AddOutputFilterByType DEFLATE application/atom+xml \ 169 | application/javascript \ 170 | application/json \ 171 | application/rss+xml \ 172 | application/vnd.ms-fontobject \ 173 | application/x-font-ttf \ 174 | application/xhtml+xml \ 175 | application/xml \ 176 | font/opentype \ 177 | image/svg+xml \ 178 | image/x-icon \ 179 | text/css \ 180 | text/html \ 181 | text/plain \ 182 | text/x-component \ 183 | text/xml 184 | 185 | 186 | 187 | 188 | 189 | # ---------------------------------------------------------------------- 190 | # Expires headers (for better cache control) 191 | # ---------------------------------------------------------------------- 192 | 193 | # These are pretty far-future expires headers. 194 | # They assume you control versioning with filename-based cache busting 195 | # Additionally, consider that outdated proxies may miscache 196 | # www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ 197 | 198 | # If you don't use filenames to version, lower the CSS and JS to something like 199 | # "access plus 1 week". 200 | 201 | 202 | ExpiresActive on 203 | 204 | # Perhaps better to whitelist expires rules? Perhaps. 205 | ExpiresDefault "access plus 1 month" 206 | 207 | # cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5) 208 | ExpiresByType text/cache-manifest "access plus 0 seconds" 209 | 210 | # Your document html 211 | ExpiresByType text/html "access plus 0 seconds" 212 | 213 | # Data 214 | ExpiresByType text/xml "access plus 0 seconds" 215 | ExpiresByType application/xml "access plus 0 seconds" 216 | ExpiresByType application/json "access plus 0 seconds" 217 | 218 | # Feed 219 | ExpiresByType application/rss+xml "access plus 1 hour" 220 | ExpiresByType application/atom+xml "access plus 1 hour" 221 | 222 | # Favicon (cannot be renamed) 223 | ExpiresByType image/x-icon "access plus 1 week" 224 | 225 | # Media: images, video, audio 226 | ExpiresByType image/gif "access plus 1 month" 227 | ExpiresByType image/png "access plus 1 month" 228 | ExpiresByType image/jpeg "access plus 1 month" 229 | ExpiresByType video/ogg "access plus 1 month" 230 | ExpiresByType audio/ogg "access plus 1 month" 231 | ExpiresByType video/mp4 "access plus 1 month" 232 | ExpiresByType video/webm "access plus 1 month" 233 | 234 | # HTC files (css3pie) 235 | ExpiresByType text/x-component "access plus 1 month" 236 | 237 | # Webfonts 238 | ExpiresByType application/x-font-ttf "access plus 1 month" 239 | ExpiresByType font/opentype "access plus 1 month" 240 | ExpiresByType application/x-font-woff "access plus 1 month" 241 | ExpiresByType image/svg+xml "access plus 1 month" 242 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 243 | 244 | # CSS and JavaScript 245 | ExpiresByType text/css "access plus 1 year" 246 | ExpiresByType application/javascript "access plus 1 year" 247 | 248 | 249 | 250 | 251 | # ---------------------------------------------------------------------- 252 | # Prevent mobile network providers from modifying your site 253 | # ---------------------------------------------------------------------- 254 | 255 | # The following header prevents modification of your code over 3G on some 256 | # European providers. 257 | # This is the official 'bypass' suggested by O2 in the UK. 258 | 259 | # 260 | # Header set Cache-Control "no-transform" 261 | # 262 | 263 | 264 | # ---------------------------------------------------------------------- 265 | # ETag removal 266 | # ---------------------------------------------------------------------- 267 | 268 | # FileETag None is not enough for every server. 269 | 270 | Header unset ETag 271 | 272 | 273 | # Since we're sending far-future expires, we don't need ETags for 274 | # static content. 275 | # developer.yahoo.com/performance/rules.html#etags 276 | FileETag None 277 | 278 | 279 | # ---------------------------------------------------------------------- 280 | # Stop screen flicker in IE on CSS rollovers 281 | # ---------------------------------------------------------------------- 282 | 283 | # The following directives stop screen flicker in IE on CSS rollovers - in 284 | # combination with the "ExpiresByType" rules for images (see above). 285 | 286 | # BrowserMatch "MSIE" brokenvary=1 287 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 288 | # BrowserMatch "Opera" !brokenvary 289 | # SetEnvIf brokenvary 1 force-no-vary 290 | 291 | 292 | # ---------------------------------------------------------------------- 293 | # Set Keep-Alive Header 294 | # ---------------------------------------------------------------------- 295 | 296 | # Keep-Alive allows the server to send multiple requests through one 297 | # TCP-connection. Be aware of possible disadvantages of this setting. Turn on 298 | # if you serve a lot of static content. 299 | 300 | # 301 | # Header set Connection Keep-Alive 302 | # 303 | 304 | 305 | # ---------------------------------------------------------------------- 306 | # Cookie setting from iframes 307 | # ---------------------------------------------------------------------- 308 | 309 | # Allow cookies to be set from iframes (for IE only) 310 | # If needed, specify a path or regex in the Location directive. 311 | 312 | # 313 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 314 | # 315 | 316 | 317 | # ---------------------------------------------------------------------- 318 | # Start rewrite engine 319 | # ---------------------------------------------------------------------- 320 | 321 | # Turning on the rewrite engine is necessary for the following rules and 322 | # features. FollowSymLinks must be enabled for this to work. 323 | 324 | # Some cloud hosting services require RewriteBase to be set: goo.gl/HOcPN 325 | # If using the h5bp in a subdirectory, use `RewriteBase /foo` instead where 326 | # 'foo' is your directory. 327 | 328 | # If your web host doesn't allow the FollowSymlinks option, you may need to 329 | # comment it out and use `Options +SymLinksOfOwnerMatch`, but be aware of the 330 | # performance impact: http://goo.gl/Mluzd 331 | 332 | 333 | Options +FollowSymlinks 334 | # Options +SymLinksIfOwnerMatch 335 | RewriteEngine On 336 | # RewriteBase / 337 | 338 | 339 | 340 | # ---------------------------------------------------------------------- 341 | # Suppress or force the "www." at the beginning of URLs 342 | # ---------------------------------------------------------------------- 343 | 344 | # The same content should never be available under two different URLs - 345 | # especially not with and without "www." at the beginning, since this can cause 346 | # SEO problems (duplicate content). That's why you should choose one of the 347 | # alternatives and redirect the other one. 348 | 349 | # By default option 1 (no "www.") is activated. 350 | # no-www.org/faq.php?q=class_b 351 | 352 | # If you'd prefer to use option 2, just comment out all option 1 lines 353 | # and uncomment option 2. 354 | 355 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 356 | 357 | # ---------------------------------------------------------------------- 358 | 359 | # Option 1: 360 | # Rewrite "www.example.com -> example.com". 361 | 362 | 363 | RewriteCond %{HTTPS} !=on 364 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 365 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 366 | 367 | 368 | # ---------------------------------------------------------------------- 369 | 370 | # Option 2: 371 | # Rewrite "example.com -> www.example.com". 372 | # Be aware that the following rule might not be a good idea if you use "real" 373 | # subdomains for certain parts of your website. 374 | 375 | # 376 | # RewriteCond %{HTTPS} !=on 377 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 378 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 379 | # 380 | 381 | 382 | # ---------------------------------------------------------------------- 383 | # Built-in filename-based cache busting 384 | # ---------------------------------------------------------------------- 385 | 386 | # If you're not using the build script to manage your filename version revving, 387 | # you might want to consider enabling this, which will route requests for 388 | # /css/style.20110203.css to /css/style.css 389 | 390 | # To understand why this is important and a better idea than all.css?v1231, 391 | # read: github.com/h5bp/html5-boilerplate/wiki/cachebusting 392 | 393 | # 394 | # RewriteCond %{REQUEST_FILENAME} !-f 395 | # RewriteCond %{REQUEST_FILENAME} !-d 396 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 397 | # 398 | 399 | 400 | # ---------------------------------------------------------------------- 401 | # Prevent SSL cert warnings 402 | # ---------------------------------------------------------------------- 403 | 404 | # Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent 405 | # https://www.example.com when your cert only allows https://secure.example.com 406 | 407 | # 408 | # RewriteCond %{SERVER_PORT} !^443 409 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 410 | # 411 | 412 | 413 | # ---------------------------------------------------------------------- 414 | # Prevent 404 errors for non-existing redirected folders 415 | # ---------------------------------------------------------------------- 416 | 417 | # without -MultiViews, Apache will give a 404 for a rewrite if a folder of the 418 | # same name does not exist. 419 | # webmasterworld.com/apache/3808792.htm 420 | 421 | Options -MultiViews 422 | 423 | 424 | # ---------------------------------------------------------------------- 425 | # Custom 404 page 426 | # ---------------------------------------------------------------------- 427 | 428 | # You can add custom pages to handle 500 or 403 pretty easily, if you like. 429 | # If you are hosting your site in subdirectory, adjust this accordingly 430 | # e.g. ErrorDocument 404 /subdir/404.html 431 | ErrorDocument 404 /404.html 432 | 433 | 434 | # ---------------------------------------------------------------------- 435 | # UTF-8 encoding 436 | # ---------------------------------------------------------------------- 437 | 438 | # Use UTF-8 encoding for anything served text/plain or text/html 439 | AddDefaultCharset utf-8 440 | 441 | # Force UTF-8 for a number of file formats 442 | AddCharset utf-8 .atom .css .js .json .rss .vtt .xml 443 | 444 | 445 | # ---------------------------------------------------------------------- 446 | # A little more security 447 | # ---------------------------------------------------------------------- 448 | 449 | # To avoid displaying the exact version number of Apache being used, add the 450 | # following to httpd.conf (it will not work in .htaccess): 451 | # ServerTokens Prod 452 | 453 | # "-Indexes" will have Apache block users from browsing folders without a 454 | # default document Usually you should leave this activated, because you 455 | # shouldn't allow everybody to surf through every folder on your server (which 456 | # includes rather private places like CMS system folders). 457 | 458 | Options -Indexes 459 | 460 | 461 | # Block access to "hidden" directories or files whose names begin with a 462 | # period. This includes directories used by version control systems such as 463 | # Subversion or Git. 464 | 465 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 466 | RewriteCond %{SCRIPT_FILENAME} -f 467 | RewriteRule "(^|/)\." - [F] 468 | 469 | 470 | # Block access to backup and source files. These files may be left by some 471 | # text/html editors and pose a great security danger, when anyone can access 472 | # them. 473 | 474 | Order allow,deny 475 | Deny from all 476 | Satisfy All 477 | 478 | 479 | # If your server is not already configured as such, the following directive 480 | # should be uncommented in order to set PHP's register_globals option to OFF. 481 | # This closes a major security hole that is abused by most XSS (cross-site 482 | # scripting) attacks. For more information: http://php.net/register_globals 483 | # 484 | # IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS: 485 | # 486 | # Your server does not allow PHP directives to be set via .htaccess. In that 487 | # case you must make this change in your php.ini file instead. If you are 488 | # using a commercial web host, contact the administrators for assistance in 489 | # doing this. Not all servers allow local php.ini files, and they should 490 | # include all PHP configurations (not just this one), or you will effectively 491 | # reset everything to PHP defaults. Consult www.php.net for more detailed 492 | # information about setting PHP directives. 493 | 494 | # php_flag register_globals Off 495 | 496 | # Rename session cookie to something else, than PHPSESSID 497 | # php_value session.name sid 498 | 499 | # Disable magic quotes (This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0.) 500 | # php_flag magic_quotes_gpc Off 501 | 502 | # Do not show you are using PHP 503 | # Note: Move this line to php.ini since it won't work in .htaccess 504 | # php_flag expose_php Off 505 | 506 | # Level of log detail - log all errors 507 | # php_value error_reporting -1 508 | 509 | # Write errors to log file 510 | # php_flag log_errors On 511 | 512 | # Do not display errors in browser (production - Off, development - On) 513 | # php_flag display_errors Off 514 | 515 | # Do not display startup errors (production - Off, development - On) 516 | # php_flag display_startup_errors Off 517 | 518 | # Format errors in plain text 519 | # Note: Leave this setting 'On' for xdebug's var_dump() output 520 | # php_flag html_errors Off 521 | 522 | # Show multiple occurrence of error 523 | # php_flag ignore_repeated_errors Off 524 | 525 | # Show same errors from different sources 526 | # php_flag ignore_repeated_source Off 527 | 528 | # Size limit for error messages 529 | # php_value log_errors_max_len 1024 530 | 531 | # Don't precede error with string (doesn't accept empty string, use whitespace if you need) 532 | # php_value error_prepend_string " " 533 | 534 | # Don't prepend to error (doesn't accept empty string, use whitespace if you need) 535 | # php_value error_append_string " " 536 | 537 | # Increase cookie security 538 | 539 | php_value session.cookie_httponly true 540 | 541 | --------------------------------------------------------------------------------