├── blog ├── __init__.py ├── utils │ ├── __init__.py │ └── paginator.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── autobackup.py ├── migrations │ ├── __init__.py │ ├── .gitignore │ └── 0001_initial.py ├── .gitignore ├── templatetags │ ├── __init__.py │ └── globaltags.py ├── apps.py ├── templates │ ├── robots.txt │ ├── blog │ │ ├── blog_search.html │ │ ├── blog_posts_tag.html │ │ ├── blog_posts_author.html │ │ ├── blog_contact.html │ │ ├── blog_sitemap.html │ │ ├── blog_page.html │ │ ├── blog_trending_posts.html │ │ ├── blog_home.html │ │ └── blog_detail.html │ ├── maintenance.html │ ├── includes │ │ ├── menu.html │ │ └── sidebar.html │ ├── error_page.html │ └── base.html ├── forms.py ├── feed.py ├── urls.py ├── admin.py ├── models.py └── views.py ├── blogproject ├── __init__.py ├── .gitignore ├── wsgi.py ├── urls.py └── settings.py ├── media ├── .gitignore └── gallery │ └── .gitignore ├── .gitignore ├── autobackupjsondb └── .gitignore ├── static ├── .gitignore └── assets │ ├── icons │ ├── avatar.jpeg │ ├── favicon.ico │ └── file-icon.png │ ├── images │ └── avatar.jpg │ ├── css │ ├── app.css │ ├── highlight.css │ ├── blog.css │ ├── style.css │ └── bootstrap-theme.min.css │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ └── js │ ├── docs.min.js │ └── bootstrap.min.js ├── __screenshot ├── 3_admin.png ├── 1_homepage.png ├── 2_detail_post.png ├── 4_admin_posts.png ├── 6_admin_gallery.png └── 5_admin_post_editor.png ├── requirements.txt ├── manage.py ├── CHANGELOG.md ├── README.md └── LICENSE /blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blogproject/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/* 2 | -------------------------------------------------------------------------------- /blog/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/.gitignore: -------------------------------------------------------------------------------- 1 | covers/* 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | db.sqlite3 3 | -------------------------------------------------------------------------------- /autobackupjsondb/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | -------------------------------------------------------------------------------- /blog/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog/migrations/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | -------------------------------------------------------------------------------- /media/gallery/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | -------------------------------------------------------------------------------- /blogproject/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/* 2 | local_settings.py 3 | -------------------------------------------------------------------------------- /static/.gitignore: -------------------------------------------------------------------------------- 1 | admin/* 2 | cms/* 3 | redactor/* 4 | suit/* 5 | -------------------------------------------------------------------------------- /blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | name = 'blog' 6 | -------------------------------------------------------------------------------- /__screenshot/3_admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/__screenshot/3_admin.png -------------------------------------------------------------------------------- /__screenshot/1_homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/__screenshot/1_homepage.png -------------------------------------------------------------------------------- /__screenshot/2_detail_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/__screenshot/2_detail_post.png -------------------------------------------------------------------------------- /__screenshot/4_admin_posts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/__screenshot/4_admin_posts.png -------------------------------------------------------------------------------- /__screenshot/6_admin_gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/__screenshot/6_admin_gallery.png -------------------------------------------------------------------------------- /blog/templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: Mediapartners-Google 2 | Disallow: 3 | 4 | User-agent: * 5 | Disallow: /search 6 | Allow: / 7 | -------------------------------------------------------------------------------- /static/assets/icons/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/icons/avatar.jpeg -------------------------------------------------------------------------------- /static/assets/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/icons/favicon.ico -------------------------------------------------------------------------------- /static/assets/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/images/avatar.jpg -------------------------------------------------------------------------------- /static/assets/icons/file-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/icons/file-icon.png -------------------------------------------------------------------------------- /__screenshot/5_admin_post_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/__screenshot/5_admin_post_editor.png -------------------------------------------------------------------------------- /static/assets/css/app.css: -------------------------------------------------------------------------------- 1 | .tab-content { 2 | margin-top: 20px; 3 | } 4 | .tengah { 5 | text-align: center; 6 | } 7 | .card { 8 | margin-top: 20px; 9 | } -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agusmakmun/Django-Blog-Python-Learning/HEAD/static/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.1 2 | django-disqus==0.5 3 | django-import-export==0.5.0 4 | django-nocaptcha-recaptcha==0.0.19 5 | django-suit==0.2.21 6 | django-wysiwyg-redactor==0.4.9.1 7 | mock==2.0.0 8 | pbr==1.10.0 9 | Pillow==3.3.1 10 | psycopg2==2.6.2 11 | pytz==2016.6.1 12 | six==1.10.0 13 | tablib==0.11.2 14 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_search.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_home.html" %} 2 | {% block title %}Posts under query {{ query }} - {{ block.super }}{% endblock %} 3 | {% block canonical_url %}https://{{ request.get_host }}{% url 'search_posts_page' %}{% endblock %} 4 | {% block message_info_homepage %} 5 |

Post under query: "{{ query }}"

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_posts_tag.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_home.html" %} 2 | {% block title %}Posts under tag {{ tag }} - {{ block.super }}{% endblock %} 3 | {% block canonical_url %}https://{{ request.get_host }}{% url 'tag_posts_page' slug=tag.slug %}{% endblock %} 4 | {% block message_info_homepage %} 5 |

Post under tag: "{{ tag }}"

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_posts_author.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_home.html" %} 2 | {% block title %}Posts under author {{ author }} - {{ block.super }}{% endblock %} 3 | {% block canonical_url %}https://{{ request.get_host }}{% url 'author_posts_page' username=author.user.username %}{% endblock %} 4 | {% block message_info_homepage %} 5 |

Post under author: "{{ author }}"

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /blogproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for blogproject project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blogproject.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /blog/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from nocaptcha_recaptcha.fields import NoReCaptchaField 3 | 4 | 5 | class ContactForm(forms.Form): 6 | cst = { 7 | 'class': 'form-control cst__radius', 8 | 'required': 'required' 9 | } 10 | email = forms.EmailField( 11 | required=True, 12 | widget=forms.TextInput(attrs=cst) 13 | ) 14 | subject = forms.CharField( 15 | required=True, 16 | widget=forms.TextInput(attrs=cst) 17 | ) 18 | message = forms.CharField( 19 | required=True, 20 | widget=forms.Textarea(attrs=cst) 21 | ) 22 | captcha = NoReCaptchaField() 23 | -------------------------------------------------------------------------------- /static/assets/css/highlight.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;background:white;padding:0.5em;color:#333333;overflow-x:auto} 2 | .hljs-comment,.hljs-meta{color:#969896} 3 | .hljs-string,.hljs-variable,.hljs-template-variable,.hljs-strong,.hljs-emphasis,.hljs-quote{color:#df5000} 4 | .hljs-keyword,.hljs-selector-tag,.hljs-type{color:#a71d5d} 5 | .hljs-literal,.hljs-symbol,.hljs-bullet,.hljs-attribute{color:#0086b3} 6 | .hljs-section,.hljs-name{color:#63a35c} 7 | .hljs-tag{color:#333333} 8 | .hljs-title,.hljs-attr,.hljs-selector-id,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo{color:#3C5BDA} 9 | .hljs-addition{color:#55a532;background-color:#eaffea} 10 | .hljs-deletion{color:#bd2c00;background-color:#ffecec} 11 | .hljs-link{text-decoration:underline} 12 | -------------------------------------------------------------------------------- /blog/templates/maintenance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Python Learning 4 | 12 | 13 |
14 |

We’ll be back soon!

15 |
16 |

Sorry for the inconvenience but we’re performing some maintenance at the moment. If you need to you can always contact us, otherwise we’ll be back online shortly!

17 |

— The Team

18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /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", "blogproject.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /blog/feed.py: -------------------------------------------------------------------------------- 1 | from django.contrib.syndication.views import Feed 2 | from django.utils.feedgenerator import Rss201rev2Feed 3 | from django.core.urlresolvers import reverse 4 | from blog.models import Post 5 | 6 | 7 | class CorrectMimeTypeFeed(Rss201rev2Feed): 8 | mime_type = 'application/xml' 9 | 10 | 11 | class LatestPosts(Feed): 12 | feed_type = CorrectMimeTypeFeed 13 | 14 | title = "Feed Blog Posts" 15 | link = "/feed/" 16 | description = "Latest Feed Blog Posts" 17 | 18 | def author_name(self): 19 | return "Summon Agus" 20 | 21 | def items(self): 22 | return Post.objects.published()[:10] 23 | 24 | def item_title(self, item): 25 | return item.title 26 | 27 | def item_description(self, item): 28 | return item.description 29 | 30 | def item_author_name(self, item): 31 | return item.author 32 | 33 | def item_link(self, item): 34 | return reverse('detail_post_page', args=[item.slug]) 35 | 36 | def item_pubdate(self, item): 37 | return item.modified 38 | -------------------------------------------------------------------------------- /blog/templatetags/globaltags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from blog.models import * 3 | 4 | register = template.Library() 5 | 6 | ''' 7 | 10 popular tags for sidebar. 8 | Usage for: includes/menus.html 9 | 10 | {% load globaltags %} 11 | {% populartags as populartags_list %} 12 | {% for tag in populartags_list %} 13 | 14 | {{ tag.title }} {{ tag.total }} 15 | 16 | {% empty %} 17 |

No tags yet!

18 | {% endfor %} 19 | ''' 20 | 21 | 22 | @register.assignment_tag 23 | def populartags(): 24 | tags_queryset = Tag.objects.all() 25 | mapping = [ 26 | { 27 | 'tag': tag, 28 | 'total': Post.objects.published().filter(tags__slug=tag.slug).count() 29 | } for tag in tags_queryset 30 | ] 31 | mapping.sort(key=lambda x: int(x['total']), reverse=True) 32 | return mapping[:10] 33 | 34 | 35 | @register.assignment_tag 36 | def recentposts(): 37 | posts = Post.objects.published() 38 | return posts[:5] 39 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_contact.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Contact - {{ block.super }}{% endblock %} 3 | {% block canonical_url %}https://{{ request.get_host }}{% url 'contact_page' %}{% endblock %} 4 | 5 | {% block content %} 6 |
7 |

Contact

8 | {% if success %} 9 | 10 |

{{ success }}

11 | {% else %} 12 |

*) Please type again your email on body message.
13 | { If this form can not work, contact me at Facebook Chat }

14 | 15 |
16 | {% csrf_token %} 17 | {{ form.as_p }} 18 |
19 | 20 |
21 |
22 | {% endif %} 23 |
24 | 25 | {% if request.GET.subject %} 26 | 29 | {% endif %} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /blogproject/urls.py: -------------------------------------------------------------------------------- 1 | """blogproject URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.contrib import admin 18 | from django.conf.urls import include, url 19 | from django.conf.urls.static import static 20 | 21 | from django.conf.urls import ( 22 | handler400, handler403, handler404, handler500 23 | ) 24 | handler400 = 'blog.views.handler400' 25 | handler403 = 'blog.views.handler403' 26 | handler404 = 'blog.views.handler404' 27 | handler500 = 'blog.views.handler500' 28 | 29 | urlpatterns = [ 30 | url(r'^admin/', admin.site.urls), 31 | url(r'^redactor/', include('redactor.urls')), 32 | url(r'^', include('blog.urls')), 33 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 34 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_sitemap.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Sitemap - {{ block.super }} {% endblock %} 3 | {% block canonical_url %}{{ block.super }}/sitemap/{% endblock %} 4 | 5 | {% block content %} 6 |
7 |

Sitemap

8 | {% if object_list %} 9 | 14 | {% endif %} 15 |
16 | 17 | 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ object.title }} - {{ block.super }}{% endblock %} 3 | {% block canonical_url %}https://{{ request.get_host }}{% url 'detail_page' slug=object.slug %}{% endblock %} 4 | 5 | {% block meta_seo %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% endblock %} 21 | 22 | {% block content %} 23 |
24 |

{{ object.title }}

25 |

26 | Created: {{ object.created }} - Modified: {{ object.modified }} - 27 | By: {{ object.author }} 28 |

29 | {{ object.description|safe }} 30 |
31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.views.generic import TemplateView 3 | from django.contrib.sitemaps import GenericSitemap 4 | from django.contrib.sitemaps.views import sitemap 5 | from blog.views import * 6 | from blog.feed import LatestPosts 7 | 8 | info_dict = { 9 | 'queryset': Post.objects.all(), 10 | 'date_field': 'modified', 11 | } 12 | 13 | # Removed url pattern for `slug` to `[\w\-]+` and not `\S+` 14 | # Because it will getting error 404 if use for '/' (homepage) and '/' (page/else) 15 | # But, makesure the `url page` placed at the bottom from other urls. 16 | # Example: 17 | # good: r'^(?P[\w\-]+)/$' 18 | # bad: r'^(?P\S+)/$' 19 | # thanks to: http://stackoverflow.com/a/30271379/6396981 20 | 21 | urlpatterns = [ 22 | # Handler for Maintenance mode. 23 | # url(r'^$', TemplateView.as_view(template_name='maintenance.html', content_type='text/html')), 24 | 25 | url(r'^$', HomepageView.as_view(), name='homepage'), 26 | url(r'^blog/(?P[\w\-]+)/$', DetailPostView.as_view(), name='detail_post_page'), 27 | url(r'^search/$', SearchPostsView.as_view(), name='search_posts_page'), 28 | url(r'^author/(?P[\w\-]+)/$', AuthorPostsView.as_view(), name='author_posts_page'), 29 | url(r'^tag/(?P[\w\-]+)/$', TagPostsView.as_view(), name='tag_posts_page'), 30 | 31 | url(r'^feed/$', LatestPosts(), name="feed"), 32 | url(r'^sitemap\.xml$', sitemap, {'sitemaps': {'blog': GenericSitemap( 33 | info_dict, priority=0.6)}}, name='django.contrib.sitemaps.views.sitemap'), 34 | url(r'^robots\.txt/$', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')), 35 | 36 | url(r'^sitemap/$', SitemapView.as_view(), name='sitemap_page'), 37 | url(r'^contact/$', ContactView.as_view(), name='contact_page'), 38 | url(r'^trending/$', TrendingPostsView.as_view(), name='trending_posts_page'), 39 | url(r'^(?P[\w\-]+)/$', DetailPageView.as_view(), name='detail_page'), 40 | ] 41 | -------------------------------------------------------------------------------- /blog/templates/includes/menu.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 40 |
41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v.3.8 2 | * Fixed trending posts by week. 3 | * Removed pkg-resources 4 | 5 | ### v.3.7 6 | * Added feature blog post json format: `/?format=json` 7 | 8 | ``` 9 | curl -X GET \ 10 | -H "Accept: application/json" \ 11 | https://python.web.id/blog/django-redirect-http-to-https/?format=json 12 | ``` 13 | 14 | * Update for fixed draft post 15 | 16 | ### v.3.6 17 | * Added feature auto backup to the json file 18 | * Added feature highlight pre 19 | * Fixed draft post 20 | * Fixed typo, sidebar, detail, css, or else 21 | 22 | ### v.3.5 23 | * Implement Standard [PEP8](https://www.python.org/dev/peps/pep-0008/) for Python. 24 | * Implement CBV (Class Bassed View). 25 | * Migrated from Python 2.7 to [Python 3.5](https://docs.python.org/3/) 26 | * Migrated from Djagno 1.8 to [Django 1.10](https://docs.djangoproject.com/en/1.10/) 27 | * Migrated [Django wp-admin](https://github.com/barszczmm/django-wpadmin) to [Django suit](https://github.com/darklow/django-suit). 28 | * Migrated [Django Ckeditor](https://github.com/django-ckeditor/django-ckeditor) to [Django Redactor](https://github.com/douglasmiranda/django-wysiwyg-redactor). 29 | * Added [Django nocaptcha recaptcha](https://github.com/ImaginaryLandscape/django-nocaptcha-recaptcha) for contact form. 30 | * Added Page for tranding posts by visitor. 31 | * Added Feature export and import using [Django Import Export](https://github.com/django-import-export/django-import-export) 32 | * Changed Gallery Upload to only once attachment field. 33 | * Added custom template for Error page, Maintenance mode, and much more... 34 | 35 | ### v.3.4 36 | 37 | * Fixed MultipleObjectsReturned 38 | 39 | ### v.3.3 40 | 41 | * Fix locate of handlers 42 | 43 | ### v.3.2 44 | 45 | * Fix handling error 400, 403, 404, 500 46 | 47 | ### v.3.1 48 | 49 | * Fixed typo 50 | 51 | ### v.3.0 52 | 53 | * Fixed DEV-OPS and Markdown. 54 | 55 | ### v.2.1.1 56 | 57 | * adding new feature for output post with json format (example: https://python.web.id/json-posts/48/, docs: https://python.web.id/blog/new-feature-output-post-json-format-django-blog-python-learning-v211/) 58 | 59 | * adding Simple Django HIT Counter (docs: https://python.web.id/blog/how-build-simple-django-hit-counter/) 60 | 61 | * modified RSS syndication (docs: https://python.web.id/blog/building-an-rss-feed-for-your-django-content/) 62 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_trending_posts.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Trending - {{ block.super }} {% endblock %} 3 | {% block canonical_url %}{{ block.super }}/trending/{% endblock %} 4 | 5 | {% block content %} 6 |
7 | 8 |   Choice Filter Posts 9 | 10 | 16 | 20 |
21 |
22 | 23 | {% for object in object_list %} 24 |
25 |

{{ object.title }}

26 |

27 | Created: {{ object.created }} - 28 | By: {{ object.author }} - 29 | Tagged under: 30 | {% for tag in object.tags.all %} 31 | {{ tag }}{% if not forloop.last %}, {% endif %} 32 | {% empty %}Uncategories 33 | {% endfor %} 34 |

35 | {% if object.cover %} 36 |
37 | 38 | {{ object.title }} 39 | 40 |
41 | {% endif %} 42 | {{ object.description|safe|striptags|truncatewords:"50"|linebreaks }} 43 | 44 | Read More → 45 | 46 |
47 |
48 | {% empty %} 49 |

Posts does not exist!

50 | {% endfor %} 51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /blog/templates/includes/sidebar.html: -------------------------------------------------------------------------------- 1 | {% load disqus_tags %} 2 | {% load globaltags %} 3 | 11 | 12 | 20 | 21 | 30 | 31 | 35 | 36 | 45 | 46 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Django-Blog-Python-Learning 2 | ------- 3 | 4 | Release Source Code of Django Blog Python Learning v.3.8 5 | 6 | ### Demo: 7 | - [https://python.web.id](https://python.web.id) 8 | 9 | ### Technologies 10 | 11 | - Django 1.10 12 | - Python 3.5 13 | - Postgresql 14 | 15 | ### Features 16 | 17 | - Django Suit 18 | - Django Wysiwyg Redactor 19 | - Django disqus for comments. 20 | - Face 90% bootstrap. 21 | - Admin Site: User, Post, Page, Author, Tag & Gallery. 22 | - Pagination posts, Sitemap page, Search posts page, Display posts under tags, Display posts under Author. 23 | - Related posts 24 | - Next and previous for post. 25 | - sitemap.xml, feed 26 | - Contact form. 27 | - About author in the botom of posts. 28 | - Visitor Counter 29 | - Auto backup to the json file 30 | - Json post view 31 | - much more... 32 | 33 | ### Instalation 34 | 35 | I assume you already setup your django development with virtual enviroment (virtualenv). 36 | 37 | **1. Create virtual enviroment and activate it.** 38 | 39 | ``` 40 | $ virtualenv --python=/usr/bin/python3 yourenv 41 | $ source bin/activate 42 | ``` 43 | 44 | **2. Cloning this project** 45 | 46 | ``` 47 | $ git clone git@github.com:agusmakmun/Django-Blog-Python-Learning.git 48 | ``` 49 | 50 | **3. Install all requirements** 51 | 52 | > If you getting an error, `error: command 'i686-linux-gnu-gcc' failed with exit status 1` 53 | > please install first the python3-dev 54 | > using command `sudo apt-get install python3-dev` for all-deb, or use different method. 55 | > This probelm specialy for `pip3 install psycopg2`. 56 | > For more, please checkout this answer: http://stackoverflow.com/a/11094752/6396981 57 | 58 | ``` 59 | $ pip install -r requirements.txt 60 | ``` 61 | 62 | **4. Database migrations** 63 | 64 | ``` 65 | $ ./manage.py makemigrations 66 | $ ./manage.py migrate 67 | ``` 68 | 69 | **5. Run the server** 70 | 71 | ``` 72 | $ ./manage.py runserver 73 | ``` 74 | ------- 75 | 76 | ### Screenshot: 77 | 78 | #### Homepage 79 | 80 | ![Homepage](__screenshot/1_homepage.png "Homepage") 81 | 82 | #### Detail Post 83 | 84 | ![Detail Post](__screenshot/2_detail_post.png "Detail Post") 85 | 86 | #### Admin Dashboard _(using django suit)_ 87 | 88 | ![Admin Dashboard](__screenshot/3_admin.png "Admin Dashboard") 89 | 90 | #### All Posts on the Admin Dashboard & _Included Django Import Export_ 91 | 92 | ![All Posts on the Admin Dashboard & Included Django Import Export](__screenshot/4_admin_posts.png "All Posts on the Admin Dashboard & Included Django Import Export") 93 | 94 | #### Tags Filter and Redactor Editor 95 | 96 | ![Tags Filter and Redactor Editor](__screenshot/5_admin_post_editor.png "Tags Filter and Redactor Editor") 97 | 98 | #### Gallery File & Images 99 | 100 | ![Gallery](__screenshot/6_admin_gallery.png "Gallery") 101 | 102 | ### Update and Commits 103 | 104 | - https://github.com/agusmakmun/Django-Blog-Python-Learning/commits/master 105 | 106 | ### Stack Problem 107 | 108 | - https://python.web.id/tag/django/ 109 | - https://github.com/agusmakmun/Some-Examples-of-Simple-Python-Script/tree/master/Django 110 | 111 | 112 | ### ChangeLog 113 | 114 | * [CHANGELOG](CHANGELOG.md) 115 | 116 | 117 | ### License 118 | 119 | * [GPL-2.0](LICENSE) 120 | -------------------------------------------------------------------------------- /static/assets/css/blog.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | body { 6 | /*font-family: Georgia, "Times New Roman", Times, serif;*/ 7 | color: #555; 8 | } 9 | 10 | h1, .h1, 11 | h2, .h2, 12 | h3, .h3, 13 | h4, .h4, 14 | h5, .h5, 15 | h6, .h6 { 16 | margin-top: 0; 17 | /*font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;*/ 18 | font-weight: normal; 19 | color: #333; 20 | } 21 | 22 | 23 | /* 24 | * Override Bootstrap's default container. 25 | 26 | 27 | @media (min-width: 1200px) { 28 | .container { 29 | width: 970px; 30 | } 31 | } 32 | */ 33 | 34 | /* 35 | * Masthead for nav 36 | */ 37 | 38 | .blog-masthead { 39 | background-color: #428bca; 40 | -webkit-box-shadow: inset 0 -2px 5px rgba(0,0,0,.1); 41 | box-shadow: inset 0 -2px 5px rgba(0,0,0,.1); 42 | } 43 | 44 | /* Nav links */ 45 | .blog-nav-item { 46 | position: relative; 47 | display: inline-block; 48 | padding: 10px; 49 | font-weight: 500; 50 | color: #cdddeb; 51 | } 52 | .blog-nav-item:hover, 53 | .blog-nav-item:focus { 54 | color: #fff; 55 | text-decoration: none; 56 | } 57 | 58 | /* Active state gets a caret at the bottom */ 59 | .blog-nav .active { 60 | color: #fff; 61 | } 62 | .blog-nav .active:after { 63 | position: absolute; 64 | bottom: 0; 65 | left: 50%; 66 | width: 0; 67 | height: 0; 68 | margin-left: -5px; 69 | vertical-align: middle; 70 | content: " "; 71 | border-right: 5px solid transparent; 72 | border-bottom: 5px solid; 73 | border-left: 5px solid transparent; 74 | } 75 | 76 | 77 | /* 78 | * Blog name and description 79 | */ 80 | 81 | /*.blog-header { 82 | padding-top: 20px; 83 | padding-bottom: 20px; 84 | }*/ 85 | .blog-title { 86 | margin-top: 30px; 87 | margin-bottom: 0; 88 | /* font-size: 60px; */ 89 | font-weight: normal; 90 | } 91 | .blog-description { 92 | font-size: 20px; 93 | color: #999; 94 | } 95 | 96 | .related { 97 | margin-top: 2em; 98 | } 99 | /* 100 | * Main column and sidebar layout 101 | */ 102 | 103 | .blog-main { 104 | /* font-size: 18px; */ 105 | line-height: 1.5; 106 | } 107 | 108 | /* Sidebar modules for boxing content */ 109 | .sidebar-module { 110 | padding: 15px; 111 | margin: 0 -15px 15px; 112 | } 113 | .sidebar-module-inset { 114 | padding: 15px; 115 | background-color: #f5f5f5; 116 | border-radius: 4px; 117 | } 118 | .sidebar-module-inset p:last-child, 119 | .sidebar-module-inset ul:last-child, 120 | .sidebar-module-inset ol:last-child { 121 | margin-bottom: 0; 122 | } 123 | 124 | 125 | 126 | /* Pagination */ 127 | .pager { 128 | /*margin-bottom: 60px;*/ 129 | text-align: left; 130 | } 131 | .pager > li > a { 132 | width: 140px; 133 | padding: 10px 20px; 134 | text-align: center; 135 | border-radius: 30px; 136 | } 137 | 138 | 139 | /* 140 | * Blog posts 141 | */ 142 | 143 | .post { 144 | margin-bottom: 25px; 145 | overflow-x: hidden; 146 | } 147 | div.post h2:first-child { 148 | margin-bottom: 5px; 149 | /*font-size: 40px;*/ 150 | font-size: 20px; 151 | } 152 | div.post h2:first-child a:hover { 153 | text-decoration: none; 154 | } 155 | .meta { 156 | margin-bottom: 20px; 157 | color: #999; 158 | } 159 | 160 | 161 | /* 162 | * Footer 163 | */ 164 | 165 | .blog-footer { 166 | padding: 40px 0; 167 | color: #999; 168 | text-align: center; 169 | background-color: #f9f9f9; 170 | border-top: 1px solid #e5e5e5; 171 | } 172 | .blog-footer p:last-child { 173 | margin-bottom: 0; 174 | } 175 | -------------------------------------------------------------------------------- /blog/utils/paginator.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import (Paginator, EmptyPage, PageNotAnInteger) 2 | 3 | 4 | class GenericPaginator(object): 5 | """ 6 | Custom dinamic paginations for all views bassed in `generic.ListView` 7 | 8 | 1. app/views.py 9 | context_data['page_range'] = GenericPaginator( 10 | self.get_queryset(), 11 | self.paginate_by, 12 | self.request.GET.get('page') 13 | ).get_page_range() 14 | 15 | 2. app/templates/name.html 16 | {% block pagination %} 17 | {% if is_paginated %}{# `is_paginated` is default bassed in `generic.ListView` #} 18 |

19 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. 20 |

21 | 42 | {% endif %} 43 | {% endblock %}{# end block pagination #} 44 | """ 45 | 46 | def __init__(self, queryset, numb_pages, request_page): 47 | # egg: models.Video.objects.published() 48 | self.queryset = queryset 49 | 50 | # egg: int 6 `is number per page`. or from param: `paginate_by` in `generic.ListView`. 51 | self.numb_pages = numb_pages 52 | 53 | # egg: self.request.GET.get('page') 54 | self.request_page = request_page 55 | 56 | def get_page_range(self): 57 | paginator = Paginator(self.queryset, self.numb_pages) 58 | page = self.request_page 59 | try: 60 | tutorials = paginator.page(page) 61 | except PageNotAnInteger: 62 | tutorials = paginator.page(1) 63 | except EmptyPage: 64 | tutorials = paginator.page(paginator.num_pages) 65 | 66 | index = tutorials.number - 1 67 | limit = 5 # limit for show range left and right of number pages 68 | max_index = len(paginator.page_range) 69 | start_index = index - limit if index >= limit else 0 70 | end_index = index + limit if index <= max_index - limit else max_index 71 | 72 | # When you return this, you will getting error 73 | # `page_range TypeError: sequence index must be integer, not 'slice'`. 74 | # Because now in django changelog, use `xrange`, and not `range`. 75 | # See this tickets: https://code.djangoproject.com/ticket/23140 76 | # >>> page_range = paginator.page_range[start_index:end_index] 77 | page_range = list(paginator.page_range)[start_index:end_index] 78 | return page_range 79 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block content %} 5 | {% block message_info_homepage %}{% endblock %} 6 | 7 | {% for object in object_list %} 8 |
9 |

{{ object.title }}

10 |

11 | By: {{ object.author }} ● 12 | at {{ object.created }} ● 13 | Posted under: 14 | {% for tag in object.tags.all %} 15 | #{{ tag }}{% if not forloop.last %}, {% endif %} 16 | {% empty %}Uncategories 17 | {% endfor %} 18 |

19 | 20 | {% if object.cover %} 21 |
22 | 23 | {{ object.title }} 24 | 25 |
26 | {% endif %} 27 | {{ object.description|safe|striptags|truncatewords:"50"|linebreaks }} 28 | 29 | Read More → 30 | 31 |
32 |
33 | {% empty %} 34 |

Posts does not exist!

35 | {% endfor %} 36 | 37 | {% if is_paginated %} 38 | {% if request.GET.q %} 39 | 58 | {% else %} 59 | 78 | {% endif %}{# endif request.GET.q #} 79 | {% endif %} 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /blog/management/commands/autobackup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import time 4 | from django.core.management.base import BaseCommand, CommandError 5 | 6 | from blog.admin import ( 7 | AuthorResource, TagResource, PostResource, 8 | GalleryResource, VisitorResource, PageResource 9 | ) 10 | from blog.models import Post 11 | 12 | ''' 13 | Refferences: 14 | - http://stackoverflow.com/a/11789141/3445802 15 | - http://stackoverflow.com/a/3287063/6396981 16 | See alseo: 17 | - https://docs.djangoproject.com/es/1.9/howto/custom-management-commands/ 18 | 19 | *) If you work with `hosting`, you can setup on `Cron Jobs`, 20 | and setup your time with command: 21 | source /path/to/yourenv/bin/activate && cd /path/to/yourenv/yourproject && ./manage.py autobackup yes 22 | 23 | 24 | *) But, if you work with VPS or SERVER, please following this command bellow: 25 | $ sudo crontab -e 26 | 27 | # Setup to daily method. 28 | [minute] [hour] [date] [month] [year] 29 | 30 | 59 23 * * * source /path/to/yourenv/bin/activate && cd /path/to/yourenv/yourproject && ./manage.py autobackup yes 31 | 32 | ''' 33 | 34 | 35 | class Command(BaseCommand): 36 | help = 'To backup the blog using django-import-export!' 37 | 38 | def add_arguments(self, parser): 39 | parser.add_argument( 40 | 'backup', 41 | help='To backup the blog using django-import-export!' 42 | ) 43 | 44 | def handle(self, *args, **options): 45 | if options['backup'] == 'yes': 46 | 47 | # Checking directory backup, exist or yet! 48 | directory_backup = "autobackupjsondb" 49 | if os.path.isdir(directory_backup) == False: 50 | os.makedirs(directory_backup) 51 | 52 | def backupMixin(resources_set, fname): 53 | filepath = '{}/{}.json'.format( 54 | directory_backup, 55 | fname 56 | ) 57 | with open(filepath, 'w') as outfile: 58 | json.dump(resources_set.json, outfile) 59 | 60 | # returning file size to 'kB' version. 61 | return int(os.path.getsize(filepath)) / 1000 62 | 63 | authorset = AuthorResource().export() 64 | fsizeAuthor = backupMixin(authorset, 'author') 65 | 66 | tagset = TagResource().export() 67 | fsizeTag = backupMixin(tagset, 'tag') 68 | 69 | postset = PostResource().export() 70 | fsizePost = backupMixin(postset, 'post') 71 | 72 | galleryset = GalleryResource().export() 73 | fsizeGallery = backupMixin(galleryset, 'gallery') 74 | 75 | visitorset = VisitorResource().export() 76 | fsizeVisitor = backupMixin(visitorset, 'visitor') 77 | 78 | pageset = PageResource().export() 79 | fsizePage = backupMixin(pageset, 'page') 80 | 81 | def backupInfo(): 82 | return ''\ 83 | '-------------------------------\n' \ 84 | '| Last backup date: {0}\n' \ 85 | '-------------------------------\n' \ 86 | '| Author : {1} kB\n' \ 87 | '| Tag : {2} kB\n' \ 88 | '| Post : {3} kB\n' \ 89 | '| Gallery : {4} kB\n' \ 90 | '| Visitor : {5} kB\n' \ 91 | '| Page : {6} kB\n' \ 92 | '-------------------------------'.format( 93 | time.strftime("%d-%m-%Y"), 94 | fsizeAuthor, fsizeTag, fsizePost, 95 | fsizeGallery, fsizeVisitor, fsizePage 96 | ) 97 | 98 | finfo = directory_backup + '/backupinfo.txt' 99 | with open(finfo, 'w') as f: 100 | f.write(backupInfo()) 101 | f.close() 102 | 103 | self.stdout.write( 104 | self.style.SUCCESS( 105 | backupInfo() 106 | ) 107 | ) 108 | else: 109 | self.stdout.write( 110 | self.style.WARNING('[-] Can not backup blog!') 111 | ) 112 | -------------------------------------------------------------------------------- /blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-09-20 04:17 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import redactor.fields 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Author', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('avatar', models.ImageField(blank=True, help_text='Upload your photo for Avatar', null=True, upload_to='gallery/avatar/%Y/%m/%d')), 25 | ('about', models.TextField()), 26 | ('website', models.URLField(blank=True, null=True)), 27 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author', to=settings.AUTH_USER_MODEL)), 28 | ], 29 | options={ 30 | 'verbose_name_plural': 'Authors', 31 | 'verbose_name': 'Detail Author', 32 | }, 33 | ), 34 | migrations.CreateModel( 35 | name='Page', 36 | fields=[ 37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('created', models.DateTimeField(auto_now_add=True)), 39 | ('modified', models.DateTimeField(auto_now=True)), 40 | ('title', models.CharField(max_length=200)), 41 | ('slug', models.SlugField(max_length=200, unique=True)), 42 | ('description', redactor.fields.RedactorField()), 43 | ('publish', models.BooleanField(default=True)), 44 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author_page', to='blog.Author')), 45 | ], 46 | options={ 47 | 'verbose_name_plural': 'Pages', 48 | 'ordering': ['-created'], 49 | 'verbose_name': 'Detail Page', 50 | }, 51 | ), 52 | migrations.CreateModel( 53 | name='Post', 54 | fields=[ 55 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 56 | ('created', models.DateTimeField(auto_now_add=True)), 57 | ('modified', models.DateTimeField(auto_now=True)), 58 | ('title', models.CharField(max_length=200)), 59 | ('slug', models.SlugField(max_length=200, unique=True)), 60 | ('cover', models.ImageField(blank=True, null=True, upload_to='gallery/covers/%Y/%m/%d')), 61 | ('description', redactor.fields.RedactorField()), 62 | ('keywords', models.CharField(blank=True, help_text='Keywords sparate by comma.', max_length=200, null=True)), 63 | ('meta_description', models.TextField(blank=True, null=True)), 64 | ('publish', models.BooleanField(default=True)), 65 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author_post', to='blog.Author')), 66 | ], 67 | options={ 68 | 'verbose_name_plural': 'Posts', 69 | 'ordering': ['-created'], 70 | 'verbose_name': 'Detail Post', 71 | }, 72 | ), 73 | migrations.CreateModel( 74 | name='Tag', 75 | fields=[ 76 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 77 | ('title', models.CharField(max_length=200)), 78 | ('slug', models.SlugField(max_length=200, unique=True)), 79 | ], 80 | options={ 81 | 'verbose_name_plural': 'Tags', 82 | 'verbose_name': 'Detail Tag', 83 | }, 84 | ), 85 | migrations.AddField( 86 | model_name='post', 87 | name='tags', 88 | field=models.ManyToManyField(to='blog.Tag'), 89 | ), 90 | ] 91 | -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django import forms 3 | from django.db.models import TextField 4 | from django.contrib.admin.widgets import FilteredSelectMultiple 5 | from django.utils.translation import ugettext_lazy as _ 6 | from django.utils.text import capfirst 7 | 8 | # Integrating the model to can import and export the data via admin dashboard. 9 | # See this docs: https://goo.gl/QR3Qqp 10 | from import_export import resources 11 | from import_export.admin import ImportExportModelAdmin 12 | from suit.widgets import AutosizedTextarea 13 | from blog.models import * 14 | 15 | 16 | class AuthorResource(resources.ModelResource): 17 | 18 | class Meta: 19 | model = Author 20 | 21 | 22 | class AuthorAdmin(ImportExportModelAdmin, admin.ModelAdmin): 23 | resource_class = AuthorResource 24 | list_display = ('user', 'website', 'about') 25 | search_fields = ['user__username', 'user__email', 'about'] 26 | list_filter = ['user__is_active', 'user__is_staff', 'user__is_superuser'] 27 | 28 | 29 | class TagResource(resources.ModelResource): 30 | 31 | class Meta: 32 | model = Tag 33 | 34 | 35 | class TagAdmin(ImportExportModelAdmin, admin.ModelAdmin): 36 | resource_class = TagResource 37 | list_display = ('title', 'slug') 38 | prepopulated_fields = {'slug': ('title',)} 39 | 40 | 41 | class TagAdminForm(forms.ModelForm): 42 | meta_description = forms.CharField( 43 | required=False, 44 | widget=AutosizedTextarea( 45 | attrs={'rows': 3, 'class': 'input-xlarge'})) 46 | 47 | tags = forms.ModelMultipleChoiceField( 48 | queryset=Tag.objects.all(), 49 | required=False, 50 | widget=FilteredSelectMultiple( 51 | verbose_name=_('Tags'), 52 | is_stacked=False 53 | ) 54 | ) 55 | 56 | class Meta: 57 | model = Post 58 | fields = '__all__' 59 | 60 | def __init__(self, *args, **kwargs): 61 | super(TagAdminForm, self).__init__(*args, **kwargs) 62 | 63 | if self.instance and self.instance.pk: 64 | self.fields['tags'].initial = self.instance.tags.all() 65 | 66 | def save(self, commit=True): 67 | post = super(TagAdminForm, self).save(commit=False) 68 | if commit: 69 | post.save() 70 | 71 | if post.pk: 72 | post.tags = self.cleaned_data['tags'] 73 | self.save_m2m() 74 | return post 75 | 76 | 77 | class PostResource(resources.ModelResource): 78 | 79 | class Meta: 80 | model = Post 81 | 82 | 83 | class PostAdmin(ImportExportModelAdmin, admin.ModelAdmin): 84 | resource_class = PostResource 85 | form = TagAdminForm 86 | list_display = ('title', 'author', 'created', 'modified', 'publish') 87 | prepopulated_fields = {'slug': ('title',)} 88 | search_fields = ['title', 'description', 'author__user__username'] 89 | list_filter = ['publish', 'author__user__username', 'created'] 90 | list_per_page = 20 91 | 92 | 93 | class PageResource(resources.ModelResource): 94 | 95 | class Meta: 96 | model = Page 97 | 98 | 99 | class PageAdmin(ImportExportModelAdmin, admin.ModelAdmin): 100 | resource_class = PageResource 101 | list_display = ('title', 'author', 'created', 'modified', 'publish') 102 | prepopulated_fields = {'slug': ('title',)} 103 | search_fields = ['title', 'description', 'author__user__username'] 104 | list_filter = ['publish', 'author__user__username', 'created'] 105 | list_per_page = 20 106 | 107 | 108 | class GalleryResource(resources.ModelResource): 109 | 110 | class Meta: 111 | model = Gallery 112 | 113 | 114 | class GalleryAdmin(ImportExportModelAdmin, admin.ModelAdmin): 115 | resource_class = GalleryResource 116 | list_display = ('check_if_image', 'title', 'created', 'modified') 117 | search_fields = ['title'] 118 | list_filter = ['created'] 119 | list_per_page = 20 120 | 121 | 122 | class VisitorResource(resources.ModelResource): 123 | 124 | class Meta: 125 | model = Visitor 126 | 127 | 128 | class VisitorAdmin(ImportExportModelAdmin, admin.ModelAdmin): 129 | resource_class = VisitorResource 130 | list_display = ('post', 'ip', 'created', 'modified') 131 | 132 | 133 | admin.site.register(Author, AuthorAdmin) 134 | admin.site.register(Tag, TagAdmin) 135 | admin.site.register(Post, PostAdmin) 136 | admin.site.register(Page, PageAdmin) 137 | admin.site.register(Gallery, GalleryAdmin) 138 | admin.site.register(Visitor, VisitorAdmin) 139 | -------------------------------------------------------------------------------- /static/assets/css/style.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Raleway'; 2 | body { 3 | font-size: 13px; 4 | font-family: 'Raleway', sans-serif; 5 | } 6 | a { 7 | -webkit-transition: color .15s cubic-bezier(.33,.66,.66,1); 8 | transition: color .15s cubic-bezier(.33,.66,.66,1); 9 | } 10 | pre { 11 | font-family: monospace; 12 | font-size: 12px; 13 | } 14 | img, iframe { 15 | max-width: 100%; 16 | } 17 | h2 { 18 | font-size: 20px; 19 | } 20 | img.cover-post { 21 | margin: 1px; 22 | border: 1px solid #ddd; 23 | padding: 2px; 24 | } 25 | .post ul { 26 | color: #555; 27 | } 28 | .message__info { 29 | background-color: #F5F5F5; 30 | padding: 10px; 31 | border: 1px solid #EFECEC; 32 | margin-bottom: 2em; 33 | text-align: center; 34 | } 35 | .btn { 36 | border: none; 37 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 38 | } 39 | .btn-primary { 40 | background-color: #337ab7; 41 | } 42 | .btn-info { 43 | background-color: #33A4B7; 44 | } 45 | .btn-warning { 46 | background-color: #f0AD4E; 47 | } 48 | .btn-success { 49 | background-color: #33B757; 50 | } 51 | .btn-danger { 52 | background-color: #B73333; 53 | } 54 | .title__bold { 55 | font-weight: bold; 56 | } 57 | .cst__radius { 58 | border-radius: 2px; 59 | } 60 | .cst__btn__search__outline { 61 | border: 1px solid #ccc; 62 | } 63 | .cst__tags { 64 | margin-bottom: .5em; 65 | text-align: left; 66 | } 67 | .cst__cover__posts { 68 | margin-bottom: 1em; 69 | max-height: 200px; 70 | overflow: hidden; 71 | } 72 | .main__container { 73 | margin-top: 8em; 74 | } 75 | .sitemap ul li { 76 | background: #FAFAFA; 77 | border-bottom: 1px solid #f0f0f0; 78 | border-top: 1px solid #fff; 79 | padding: 2px; 80 | padding-left: 5px; 81 | list-style: decimal; 82 | } 83 | .post__info { 84 | color: #999; 85 | } 86 | .author__avatar { 87 | border-radius: 50% 88 | } 89 | .sitemap ul li:nth-child(odd) { 90 | background: #FFF; 91 | } 92 | .input-group-addon { 93 | font-size: 12px; 94 | border: none; 95 | color: #fff; 96 | border-radius: 3px; 97 | text-transform: uppercase; 98 | background-color: #33A4B7; 99 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 100 | } 101 | #filter__trending__posts { 102 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 103 | border: none; 104 | } 105 | /* 106 | Code snippet by maridlcrmn for Bootsnipp.com 107 | Follow me on Twitter @maridlcrmn 108 | */ 109 | 110 | .navbar-brand { position: relative; z-index: 2; } 111 | 112 | .navbar-nav.navbar-right .btn { position: relative; z-index: 2; padding: 4px 20px; margin: 10px auto; transition: transform 0.3s; } 113 | 114 | .navbar .navbar-collapse { position: relative; overflow: hidden !important; } 115 | .navbar .navbar-collapse .navbar-right > li:last-child { padding-left: 22px; } 116 | 117 | .navbar .nav-collapse { position: absolute; z-index: 1; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding-right: 120px; padding-left: 80px; width: 100%; } 118 | .navbar.navbar-default .nav-collapse { background-color: #f8f8f8; } 119 | .navbar.navbar-inverse .nav-collapse { background-color: #222; } 120 | .navbar .nav-collapse .navbar-form { border-width: 0; box-shadow: none; } 121 | .nav-collapse>li { float: right; } 122 | 123 | .btn.btn-circle { border-radius: 50px; } 124 | .btn.btn-outline { background-color: transparent; } 125 | 126 | .navbar-nav.navbar-right .btn:not(.collapsed) { 127 | background-color: rgb(111, 84, 153); 128 | border-color: rgb(111, 84, 153); 129 | color: rgb(255, 255, 255); 130 | } 131 | 132 | .navbar.navbar-default .nav-collapse, 133 | .navbar.navbar-inverse .nav-collapse { 134 | height: auto !important; 135 | transition: transform 0.3s; 136 | transform: translate(0px,-50px); 137 | } 138 | .navbar.navbar-default .nav-collapse.in, 139 | .navbar.navbar-inverse .nav-collapse.in { 140 | transform: translate(0px,0px); 141 | } 142 | 143 | @media screen and (min-width: 768px) { 144 | .navbar-right { 145 | margin-right: auto; 146 | } 147 | } 148 | @media screen and (max-width: 767px) { 149 | .navbar .navbar-collapse .navbar-right > li:last-child { padding-left: 15px; padding-right: 15px; } 150 | 151 | .navbar .nav-collapse { margin: 7.5px auto; padding: 0; } 152 | .navbar .nav-collapse .navbar-form { margin: 0; } 153 | .nav-collapse>li { float: none; } 154 | 155 | .navbar.navbar-default .nav-collapse, 156 | .navbar.navbar-inverse .nav-collapse { 157 | transform: translate(-100%,0px); 158 | } 159 | .navbar.navbar-default .nav-collapse.in, 160 | .navbar.navbar-inverse .nav-collapse.in { 161 | transform: translate(0px,0px); 162 | } 163 | 164 | .navbar.navbar-default .nav-collapse.slide-down, 165 | .navbar.navbar-inverse .nav-collapse.slide-down { 166 | transform: translate(0px,-100%); 167 | } 168 | .navbar.navbar-default .nav-collapse.in.slide-down, 169 | .navbar.navbar-inverse .nav-collapse.in.slide-down { 170 | transform: translate(0px,0px); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.contrib.auth.models import User 4 | from django.core.urlresolvers import reverse 5 | 6 | from redactor.fields import RedactorField 7 | 8 | 9 | class TimeStampedModel(models.Model): 10 | created = models.DateTimeField(auto_now_add=True) 11 | modified = models.DateTimeField(auto_now=True) 12 | 13 | class Meta: 14 | abstract = True 15 | 16 | 17 | class Author(models.Model): 18 | user = models.ForeignKey(User, related_name='author') 19 | avatar = models.ImageField(upload_to='gallery/avatar/%Y/%m/%d', 20 | null=True, 21 | blank=True, 22 | help_text="Upload your photo for Avatar") 23 | about = models.TextField() 24 | website = models.URLField(max_length=200, blank=True, null=True) 25 | 26 | def __str__(self): 27 | return self.user.username 28 | 29 | def get_absolute_url(self): 30 | return reverse('author_posts_page', 31 | kwargs={'username': self.user.username}) 32 | 33 | class Meta: 34 | verbose_name = 'Detail Author' 35 | verbose_name_plural = 'Authors' 36 | 37 | 38 | class Tag(models.Model): 39 | title = models.CharField(max_length=200) 40 | slug = models.SlugField(max_length=200, unique=True) 41 | 42 | def __str__(self): 43 | return self.title 44 | 45 | @property 46 | def get_total_posts(self): 47 | return Post.objects.filter(tags__pk=self.pk).count() 48 | 49 | class Meta: 50 | verbose_name = 'Detail Tag' 51 | verbose_name_plural = 'Tags' 52 | 53 | 54 | class PostQuerySet(models.QuerySet): 55 | 56 | def published(self): 57 | return self.filter(publish=True) 58 | 59 | 60 | class Post(TimeStampedModel): 61 | author = models.ForeignKey(Author, related_name='author_post') 62 | title = models.CharField(max_length=200) 63 | slug = models.SlugField(max_length=200, unique=True) 64 | cover = models.ImageField(upload_to='gallery/covers/%Y/%m/%d', 65 | null=True, 66 | blank=True, 67 | help_text='Optional cover post') 68 | description = RedactorField() 69 | tags = models.ManyToManyField('Tag') 70 | keywords = models.CharField(max_length=200, null=True, blank=True, 71 | help_text='Keywords sparate by comma.') 72 | meta_description = models.TextField(null=True, blank=True) 73 | 74 | publish = models.BooleanField(default=True) 75 | objects = PostQuerySet.as_manager() 76 | 77 | def get_absolute_url(self): 78 | return reverse('detail_post_page', kwargs={'slug': self.slug}) 79 | 80 | @property 81 | def total_visitors(self): 82 | return Visitor.objects.filter(post__pk=self.pk).count() 83 | 84 | def __str__(self): 85 | return self.title 86 | 87 | class Meta: 88 | verbose_name = 'Detail Post' 89 | verbose_name_plural = 'Posts' 90 | ordering = ["-created"] 91 | 92 | 93 | class Page(TimeStampedModel): 94 | author = models.ForeignKey(Author, related_name='author_page') 95 | title = models.CharField(max_length=200) 96 | slug = models.SlugField(max_length=200, unique=True) 97 | description = RedactorField() 98 | publish = models.BooleanField(default=True) 99 | 100 | def __str__(self): 101 | return self.title 102 | 103 | # this will be an error in /admin 104 | # def get_absolute_url(self): 105 | # return reverse("page_detail", kwargs={"slug": self.slug}) 106 | 107 | class Meta: 108 | verbose_name = "Detail Page" 109 | verbose_name_plural = "Pages" 110 | ordering = ["-created"] 111 | 112 | 113 | class Gallery(TimeStampedModel): 114 | title = models.CharField(max_length=200) 115 | attachment = models.FileField(upload_to='gallery/attachment/%Y/%m/%d') 116 | 117 | def __str__(self): 118 | return self.title 119 | 120 | def check_if_image(self): 121 | if self.attachment.name.split('.')[-1].lower() \ 122 | in ['jpg', 'jpeg', 'gif', 'png']: 123 | return ('' % self.attachment.url) 124 | return ('') 125 | check_if_image.short_description = 'Attachment' 126 | check_if_image.allow_tags = True 127 | 128 | class Meta: 129 | verbose_name = 'Detail Gallery' 130 | verbose_name_plural = 'Galleries' 131 | ordering = ['-created'] 132 | 133 | 134 | class Visitor(TimeStampedModel): 135 | post = models.ForeignKey(Post, related_name='post_visitor') 136 | ip = models.CharField(max_length=40) 137 | 138 | def __str__(self): 139 | return self.post.title 140 | 141 | class Meta: 142 | verbose_name = 'Detail Visitor' 143 | verbose_name_plural = 'Visitors' 144 | ordering = ['-created'] 145 | -------------------------------------------------------------------------------- /blog/templates/error_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | {{ title }} - Python Learning 14 | 15 | 16 | 17 | 135 | 136 | 137 |
138 |
139 |
140 |
141 |
142 |
143 |

Whoops!

144 |

145 | You have an error {{ message }}. 146 |

147 |
148 |
149 | 150 | -------------------------------------------------------------------------------- /blog/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% block title %}Python Learning{% endblock %} 24 | 25 | 26 | {% block meta_seo %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {% endblock %} 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 67 | 68 |
69 | 78 | 79 | {% include "includes/menu.html" %} 80 | 81 |
82 |
83 |
84 | {% block content %}{% endblock %} 85 |
86 | 87 |
88 | {% include "includes/sidebar.html" %} 89 |
90 |
91 |
92 | 93 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /blogproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for blogproject project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'site_secret_key' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | ALLOWED_HOSTS = ['*'] 28 | SITE_ID = 1 29 | EMAIL_HOST = 'smtp.gmail.com' 30 | EMAIL_PORT = 587 31 | EMAIL_HOST_USER = 'your_email@gmail.com' 32 | EMAIL_HOST_PASSWORD = 'password' 33 | EMAIL_USE_TLS = True 34 | DEFAULT_FROM_EMAIL = EMAIL_HOST_USER 35 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 36 | FILE_UPLOAD_MAX_MEMORY_SIZE = 35000000 37 | 38 | DISQUS_API_KEY = 'your_disqus_api_key' 39 | DISQUS_WEBSITE_SHORTNAME = 'your_disques_website_name' 40 | 41 | # No Recaptha SITE_KEY and SECRET KEY 42 | # https://www.google.com/recaptcha 43 | NORECAPTCHA_SITE_KEY = "key_key_key_key" 44 | NORECAPTCHA_SECRET_KEY = "key_key_key_key" 45 | 46 | # Application definition 47 | INSTALLED_APPS = [ 48 | # Adding Django admin suit 49 | 'suit', 50 | 51 | 'django.contrib.admin', 52 | 'django.contrib.auth', 53 | 'django.contrib.contenttypes', 54 | 'django.contrib.sessions', 55 | 'django.contrib.messages', 56 | 'django.contrib.staticfiles', 57 | 58 | 'django.contrib.sitemaps', 59 | 'django.contrib.sites', 60 | 61 | 'disqus', 62 | 'redactor', 63 | 'nocaptcha_recaptcha', 64 | 'import_export', 65 | 66 | 'blog', 67 | ] 68 | 69 | # Django Suit configuration 70 | SUIT_CONFIG = { 71 | 'ADMIN_NAME': 'Python Learning', 72 | 'SEARCH_URL': '/admin/blog/post/', 73 | 'MENU': ( 74 | {'app': 'blog', 'label': 'Blog', 'models': ('post', 'tag', 'page', 'author', 'gallery', 'visitor'), 75 | 'icon': 'icon-align-left'}, 76 | '-', 77 | {'app': 'auth', 'label': 'Authentication', 78 | 'icon': 'icon-lock', 'models': ('user', 'group')}, 79 | {'app': 'sites', 'label': 'Site Config', 'icon': 'icon-leaf'}, 80 | ), 81 | 'LIST_PER_PAGE': 15 82 | } 83 | 84 | MIDDLEWARE = [ 85 | 'django.middleware.security.SecurityMiddleware', 86 | 'django.contrib.sessions.middleware.SessionMiddleware', 87 | 'django.middleware.common.CommonMiddleware', 88 | 'django.middleware.csrf.CsrfViewMiddleware', 89 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 90 | 'django.contrib.messages.middleware.MessageMiddleware', 91 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 92 | ] 93 | 94 | ROOT_URLCONF = 'blogproject.urls' 95 | 96 | TEMPLATES = [ 97 | { 98 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 99 | 'DIRS': [], 100 | 'APP_DIRS': True, 101 | 'OPTIONS': { 102 | 'context_processors': [ 103 | 'django.template.context_processors.debug', 104 | 'django.template.context_processors.request', 105 | 'django.contrib.auth.context_processors.auth', 106 | 'django.contrib.messages.context_processors.messages', 107 | ], 108 | }, 109 | }, 110 | ] 111 | 112 | WSGI_APPLICATION = 'blogproject.wsgi.application' 113 | 114 | 115 | # Database 116 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 117 | """ 118 | DATABASES = { 119 | 'default': { 120 | 'ENGINE': 'django.db.backends.sqlite3', 121 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 122 | } 123 | } 124 | """ 125 | 126 | # Config with postgresql - psql (9.5.4, server 9.3.14) 127 | # $ sudo su - postgres 128 | # $ psql 129 | # postgres=# CREATE DATABASE database_nme; 130 | # postgres=# CREATE USER database_user WITH PASSWORD 'password_user'; 131 | # postgres=# GRANT ALL PRIVILEGES ON DATABASE database_nme TO database_user; 132 | # See this docs for more; https://goo.gl/9ONJKX 133 | 134 | DATABASES = { 135 | 'default': { 136 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 137 | 'NAME': 'database_nme', 138 | 'USER': 'database_user', 139 | 'PASSWORD': 'password_user', 140 | 'HOST': '127.0.0.1', 141 | 'PORT': '5432', 142 | } 143 | } 144 | 145 | 146 | # Password validation 147 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 148 | 149 | AUTH_PASSWORD_VALIDATORS = [ 150 | { 151 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 152 | }, 153 | { 154 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 155 | }, 156 | { 157 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 158 | }, 159 | { 160 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 161 | }, 162 | ] 163 | 164 | 165 | # Internationalization 166 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 167 | 168 | LANGUAGE_CODE = 'en-us' 169 | TIME_ZONE = 'Asia/Jakarta' 170 | USE_I18N = True 171 | USE_L10N = True 172 | USE_TZ = True 173 | 174 | 175 | # Static files (CSS, JavaScript, Images) 176 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 177 | STATICFILES_DIRS = ( 178 | os.path.join(BASE_DIR, "static"), 179 | '/path/to/yourenv/blogproject/static', 180 | ) 181 | 182 | STATIC_URL = '/static/' 183 | #STATIC_ROOT = '/path/to/yourenv/blogproject/static' 184 | 185 | MEDIA_URL = '/media/' 186 | MEDIA_ROOT = '/path/to/yourenv/blogproject/media' 187 | 188 | # Editor Redactor 189 | import time 190 | REDACTOR_OPTIONS = {'lang': 'en'} 191 | REDACTOR_UPLOAD = 'uploads/' + time.strftime("%Y/%m/%d/") 192 | REDACTOR_AUTH_DECORATOR = 'django.contrib.auth.decorators.login_required' 193 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | {% block title %}{{ object.title }} - {{ block.super }}{% endblock %} 4 | {% block canonical_url %}https://{{ request.get_host }}{% url 'detail_post_page' slug=object.slug %}{% endblock %} 5 | 6 | {% block meta_seo %} 7 | 8 | 9 | 10 | {% for tag in object.tags.all %} 11 | 12 | {% endfor %} 13 | 14 | 15 | 16 | 17 | 18 | {% if object.cover %} 19 | 20 | {% endif %} 21 | 22 | 23 | 24 | {% if object.cover %} 25 | 26 | {% endif %} 27 | 28 | 29 | 30 | {% endblock %} 31 | 32 | {% block content %} 33 |
34 |

{{ object.title }}{% if object.publish == False %} DRAF{% endif %}

35 |
36 | By: {{ object.author }} ● 37 | at {{ object.created }} ● and modified at {{ object.modified }}
38 | Posted under: 39 | {% for tag in object.tags.all %} 40 | #{{ tag }}{% if not forloop.last %}, {% endif %} 41 | {% empty %}Uncategories 42 | {% endfor %} 43 |
44 | Your ip address: {{ get_client_ip }} ● views: {{ visitor_counter }} times. 45 |
46 | 47 |

48 | {% if object.cover %} 49 | 50 | {{ object.title }} 51 | {{ object.title }} 52 | 53 | {% endif %} 54 |

55 | {{ object.description|safe }} 56 | 57 | 64 | 65 | 72 | 73 |
74 |
Author
75 |
76 |
77 |
78 | 79 | {{ object.author }} 80 | 81 |
82 |
83 |

{{ object.author }}

84 | {{ object.author.about|safe }}
85 | Website: {{ object.author.website }} 86 |
87 |
88 |
89 |
90 | 91 | 103 | 104 |
    105 | 110 | 115 |
116 | 117 | {% load disqus_tags %} 118 | {% set_disqus_identifier "blogpost_" object.title %} 119 | {% set_disqus_title object.title %} 120 | {% disqus_show_comments %} 121 | 122 |
123 | 124 | 125 | 126 | 133 | {% endblock %} 134 | -------------------------------------------------------------------------------- /static/assets/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /blog/views.py: -------------------------------------------------------------------------------- 1 | import time 2 | import datetime 3 | import socket 4 | import json 5 | 6 | from django.views import generic 7 | from django.http import HttpResponse 8 | from django.shortcuts import (render, render_to_response, redirect, get_object_or_404) 9 | from django.core.mail import (send_mail, BadHeaderError) 10 | from django.core.exceptions import ObjectDoesNotExist 11 | from django.template import RequestContext 12 | from django.conf import settings 13 | from django.db.models import (Q, Count) 14 | 15 | from blog.models import * 16 | from blog.forms import ContactForm 17 | from blog.utils.paginator import GenericPaginator 18 | 19 | 20 | def handler400(request): 21 | response = render_to_response('error_page.html', {'title': '400 Bad Request', 'message': '400'}, 22 | context_instance=RequestContext(request)) 23 | response.status_code = 400 24 | return response 25 | 26 | 27 | def handler403(request): 28 | response = render_to_response('error_page.html', {'title': '403 Permission Denied', 'message': '403'}, 29 | context_instance=RequestContext(request)) 30 | response.status_code = 403 31 | return response 32 | 33 | 34 | def handler404(request): 35 | response = render_to_response('error_page.html', {'title': '404 Not Found', 'message': '404'}, 36 | context_instance=RequestContext(request)) 37 | response.status_code = 404 38 | return response 39 | 40 | 41 | def handler500(request): 42 | response = render_to_response('error_page.html', {'title': '500 Server Error', 'message': '500'}, 43 | context_instance=RequestContext(request)) 44 | response.status_code = 500 45 | return response 46 | 47 | 48 | class HomepageView(generic.ListView): 49 | queryset = Post.objects.published() 50 | template_name = 'blog/blog_home.html' 51 | paginate_by = 10 52 | 53 | def get_context_data(self, **kwargs): 54 | context_data = super(HomepageView, self).get_context_data(**kwargs) 55 | context_data['page_range'] = GenericPaginator( 56 | self.queryset, 57 | self.paginate_by, 58 | self.request.GET.get('page') 59 | ).get_page_range() 60 | return context_data 61 | 62 | 63 | class DetailPostView(generic.DetailView): 64 | model = Post 65 | template_name = 'blog/blog_detail.html' 66 | 67 | def get_client_ip(self): 68 | ip = self.request.META.get("HTTP_X_FORWARDED_FOR", None) 69 | if ip: 70 | ip = ip.split(", ")[0] 71 | else: 72 | ip = self.request.META.get("REMOTE_ADDR", "") 73 | return ip 74 | 75 | def visitorCounter(self): 76 | try: 77 | Visitor.objects.get( 78 | post=self.object, 79 | ip=self.request.META['REMOTE_ADDR'] 80 | ) 81 | except ObjectDoesNotExist: 82 | dns = str(socket.getfqdn( 83 | self.request.META['REMOTE_ADDR'] 84 | )).split('.')[-1] 85 | try: 86 | # trying for localhost: str(dns) == 'localhost', 87 | # trying for production: int(dns) 88 | if str(dns) == 'localhost': 89 | visitor = Visitor( 90 | post=self.object, 91 | ip=self.request.META['REMOTE_ADDR'] 92 | ) 93 | visitor.save() 94 | else: 95 | pass 96 | except ValueError: 97 | pass 98 | return Visitor.objects.filter(post=self.object).count() 99 | 100 | def dispatch(self, request, *args, **kwargs): 101 | obj = self.get_object() 102 | if obj.publish == False: 103 | if request.user.is_anonymous() or \ 104 | request.user != obj.author.user: 105 | return redirect('homepage') 106 | else: 107 | return super(DetailPostView, self).dispatch( 108 | request, *args, **kwargs 109 | ) 110 | elif request.GET.get('format') == 'json': 111 | get_cover = lambda obj: None if obj.cover == None \ 112 | or obj.cover == '' \ 113 | else 'https://{0}{1}{2}'.format( 114 | request.get_host(), 115 | settings.MEDIA_URL, 116 | obj.cover 117 | ) 118 | data = dict( 119 | title=obj.title, 120 | url='https://{0}/blog/{1}'.format( 121 | request.get_host(), 122 | obj.slug 123 | ), 124 | cover=get_cover(obj), 125 | author=obj.author.user.username, 126 | created=str(obj.created)[:19], 127 | modified=str(obj.modified)[:19], 128 | tags=[ 129 | {'title': t.title, 'slug': t.slug} 130 | for t in obj.tags.all() 131 | ], 132 | description=obj.description, 133 | visitors=obj.total_visitors 134 | ) 135 | return HttpResponse( 136 | json.dumps(data), 137 | content_type='application/json' 138 | ) 139 | else: 140 | return super(DetailPostView, self).dispatch( 141 | request, *args, **kwargs 142 | ) 143 | 144 | def get_context_data(self, **kwargs): 145 | context_data = super(DetailPostView, self).get_context_data(**kwargs) 146 | related_posts = Post.objects.filter( 147 | tags__in=list(self.object.tags.all()) 148 | ).exclude(id=self.object.id).distinct() 149 | context_data['related_posts'] = related_posts[:5] # limit for post 150 | context_data['get_client_ip'] = self.get_client_ip() 151 | context_data['visitor_counter'] = self.visitorCounter() 152 | return context_data 153 | 154 | 155 | class SearchPostsView(generic.ListView): 156 | template_name = 'blog/blog_search.html' 157 | paginate_by = 10 158 | 159 | def get_queryset(self): 160 | self.query = self.request.GET.get('q') 161 | try: 162 | search_posts = Post.objects.published().filter( 163 | Q(title__icontains=self.query) | 164 | Q(description__icontains=self.query) | 165 | Q(keywords__icontains=self.query) | 166 | Q(meta_description__icontains=self.query) 167 | ).order_by('-created').order_by('-id') 168 | return search_posts 169 | except: 170 | return Post.objects.published() 171 | 172 | def get_context_data(self, **kwargs): 173 | context_data = super(SearchPostsView, self).get_context_data(**kwargs) 174 | context_data['query'] = self.query 175 | context_data['page_range'] = GenericPaginator( 176 | self.get_queryset(), 177 | self.paginate_by, 178 | self.request.GET.get('page') 179 | ).get_page_range() 180 | return context_data 181 | 182 | 183 | class AuthorPostsView(generic.ListView): 184 | template_name = 'blog/blog_posts_author.html' 185 | paginate_by = 10 186 | 187 | def get_queryset(self): 188 | username = self.kwargs['username'] 189 | self.author = get_object_or_404(Author, user__username=username) 190 | posts_author = Post.objects.published().filter( 191 | author=self.author 192 | ).order_by('-created').order_by('-id') 193 | return posts_author 194 | 195 | def get_context_data(self, **kwargs): 196 | context_data = super(AuthorPostsView, self).get_context_data(**kwargs) 197 | context_data['author'] = self.author 198 | context_data['page_range'] = GenericPaginator( 199 | self.get_queryset(), 200 | self.paginate_by, 201 | self.request.GET.get('page') 202 | ).get_page_range() 203 | return context_data 204 | 205 | 206 | class TagPostsView(generic.ListView): 207 | template_name = 'blog/blog_posts_tag.html' 208 | paginate_by = 10 209 | 210 | def get_queryset(self): 211 | slug = self.kwargs['slug'] 212 | self.tag = get_object_or_404(Tag, slug=slug) 213 | results_filter = Post.objects.published().filter( 214 | tags=self.tag 215 | ).order_by('-created').order_by('-id') 216 | return results_filter 217 | 218 | def get_context_data(self, **kwargs): 219 | context_data = super(TagPostsView, self).get_context_data(**kwargs) 220 | context_data['tag'] = self.tag 221 | context_data['page_range'] = GenericPaginator( 222 | self.get_queryset(), 223 | self.paginate_by, 224 | self.request.GET.get('page') 225 | ).get_page_range() 226 | return context_data 227 | 228 | 229 | class DetailPageView(generic.DetailView): 230 | model = Page 231 | template_name = 'blog/blog_page.html' 232 | 233 | 234 | class SitemapView(generic.ListView): 235 | queryset = Post.objects.published() 236 | template_name = 'blog/blog_sitemap.html' 237 | paginate_by = 30 238 | 239 | def get_context_data(self, **kwargs): 240 | context_data = super(SitemapView, self).get_context_data(**kwargs) 241 | context_data['page_range'] = GenericPaginator( 242 | self.queryset, 243 | self.paginate_by, 244 | self.request.GET.get('page') 245 | ).get_page_range() 246 | return context_data 247 | 248 | 249 | class ContactView(generic.TemplateView): 250 | template_name = 'blog/blog_contact.html' 251 | 252 | def post(self, request, *args, **kwargs): 253 | context = self.get_context_data() 254 | if context['form'].is_valid(): 255 | cd = context['form'].cleaned_data 256 | subject = cd['subject'] 257 | from_email = cd['email'] 258 | message = cd['message'] 259 | 260 | try: 261 | send_mail( 262 | subject + " from {}".format(from_email), 263 | message, 264 | from_email, 265 | [settings.EMAIL_HOST_USER] 266 | ) 267 | except BadHeaderError: 268 | return HttpResponse('Invalid header found.') 269 | 270 | ctx = { 271 | 'success': """Thankyou, We appreciate that you've 272 | taken the time to write us. 273 | We'll get back to you very soon. 274 | Please come back and see us often.""" 275 | } 276 | return render(request, self.template_name, ctx) 277 | return super(generic.TemplateView, self).render_to_response(context) 278 | 279 | def get_context_data(self, **kwargs): 280 | context = super(ContactView, self).get_context_data(**kwargs) 281 | form = ContactForm(self.request.POST or None) 282 | context['form'] = form 283 | return context 284 | 285 | 286 | class TrendingPostsView(generic.ListView): 287 | template_name = 'blog/blog_trending_posts.html' 288 | 289 | def get_queryset(self): 290 | posts = Post.objects.published() 291 | top_posts = Visitor.objects.filter(post__in=posts)\ 292 | .values('post').annotate(visit=Count('post__id'))\ 293 | .order_by('-visit') 294 | 295 | list_pk_top_posts = [pk['post'] for pk in top_posts] 296 | filter_posts = list(Post.objects.published().filter(pk__in=list_pk_top_posts)) 297 | sorted_posts = sorted(filter_posts, key=lambda i: list_pk_top_posts.index(i.pk)) 298 | 299 | self.get_filter = self.request.GET.get('filter') 300 | now_year = time.strftime("%Y") 301 | now_month = time.strftime("%m") 302 | now_date = datetime.date.today() 303 | start_week = now_date - datetime.timedelta(7) 304 | end_week = start_week + datetime.timedelta(7) 305 | 306 | if self.get_filter == 'week': 307 | filter_posts = list(Post.objects.published() 308 | .filter(pk__in=list_pk_top_posts) 309 | .filter(created__date__range=[start_week, end_week]) 310 | ) 311 | sorted_posts = sorted(filter_posts, key=lambda i: list_pk_top_posts.index(i.pk)) 312 | 313 | elif self.get_filter == 'month': 314 | filter_posts = list(Post.objects.published() 315 | .filter(pk__in=list_pk_top_posts) 316 | .filter(created__month=now_month) 317 | .filter(created__year=now_year) 318 | ) 319 | sorted_posts = sorted(filter_posts, key=lambda i: list_pk_top_posts.index(i.pk)) 320 | 321 | elif self.get_filter == 'year': 322 | filter_posts = list(Post.objects.published() 323 | .filter(pk__in=list_pk_top_posts) 324 | .filter(created__year=now_year) 325 | ) 326 | sorted_posts = sorted(filter_posts, key=lambda i: list_pk_top_posts.index(i.pk)) 327 | 328 | else: 329 | self.get_filter == 'global' 330 | sorted_posts = sorted_posts 331 | return sorted_posts[:20] # Return 20 posts only 332 | 333 | def get_context_data(self, **kwargs): 334 | context_data = super(TrendingPostsView, self).get_context_data(**kwargs) 335 | context_data['filter'] = self.get_filter 336 | return context_data 337 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /static/assets/js/docs.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | Holder - 2.3.2 - client side image placeholders 4 | (c) 2012-2014 Ivan Malopinsky / http://imsky.co 5 | 6 | Provided under the MIT License. 7 | Commercial use requires attribution. 8 | 9 | */ 10 | var Holder=Holder||{};!function(a,b){function c(a,b,c){b=parseInt(b,10),a=parseInt(a,10);var d=Math.max(b,a),e=Math.min(b,a),f=1/12,g=Math.min(.75*e,.75*d*f);return{height:Math.round(Math.max(c.size,g))}}function d(a){var b=[];for(p in a)a.hasOwnProperty(p)&&b.push(p+":"+a[p]);return b.join(";")}function e(a){var b=a.ctx,d=a.dimensions,e=a.template,f=a.ratio,g=a.holder,h="literal"==g.textmode,i="exact"==g.textmode,j=c(d.width,d.height,e),k=j.height,l=d.width*f,m=d.height*f,n=e.font?e.font:"Arial,Helvetica,sans-serif";canvas.width=l,canvas.height=m,b.textAlign="center",b.textBaseline="middle",b.fillStyle=e.background,b.fillRect(0,0,l,m),b.fillStyle=e.foreground,b.font="bold "+k+"px "+n;var o=e.text?e.text:Math.floor(d.width)+"x"+Math.floor(d.height);if(h){var d=g.dimensions;o=d.width+"x"+d.height}else if(i&&g.exact_dimensions){var d=g.exact_dimensions;o=Math.floor(d.width)+"x"+Math.floor(d.height)}var p=b.measureText(o).width;return p/l>=.75&&(k=Math.floor(.75*k*(l/p))),b.font="bold "+k*f+"px "+n,b.fillText(o,l/2,m/2,l),canvas.toDataURL("image/png")}function f(a){var b=a.dimensions,d=a.template,e=a.holder,f="literal"==e.textmode,g="exact"==e.textmode,h=c(b.width,b.height,d),i=h.height,j=b.width,k=b.height,l=d.font?d.font:"Arial,Helvetica,sans-serif",m=d.text?d.text:Math.floor(b.width)+"x"+Math.floor(b.height);if(f){var b=e.dimensions;m=b.width+"x"+b.height}else if(g&&e.exact_dimensions){var b=e.exact_dimensions;m=Math.floor(b.width)+"x"+Math.floor(b.height)}var n=z({text:m,width:j,height:k,text_height:i,font:l,template:d});return"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(n)))}function g(a){return r.use_canvas&&!r.use_svg?e(a):f(a)}function h(a,b,c,d){var e=c.dimensions,f=c.theme,h=c.text?decodeURIComponent(c.text):c.text,i=e.width+"x"+e.height;f=h?o(f,{text:h}):f,f=c.font?o(f,{font:c.font}):f,b.setAttribute("data-src",d),c.theme=f,b.holder_data=c,"image"==a?(b.setAttribute("alt",h?h:f.text?f.text+" ["+i+"]":i),(r.use_fallback||!c.auto)&&(b.style.width=e.width+"px",b.style.height=e.height+"px"),r.use_fallback?b.style.backgroundColor=f.background:(b.setAttribute("src",g({ctx:w,dimensions:e,template:f,ratio:x,holder:c})),c.textmode&&"exact"==c.textmode&&(v.push(b),k(b)))):"background"==a?r.use_fallback||(b.style.backgroundImage="url("+g({ctx:w,dimensions:e,template:f,ratio:x,holder:c})+")",b.style.backgroundSize=e.width+"px "+e.height+"px"):"fluid"==a&&(b.setAttribute("alt",h?h:f.text?f.text+" ["+i+"]":i),"%"==e.height.slice(-1)?b.style.height=e.height:null!=c.auto&&c.auto||(b.style.height=e.height+"px"),"%"==e.width.slice(-1)?b.style.width=e.width:null!=c.auto&&c.auto||(b.style.width=e.width+"px"),("inline"==b.style.display||""===b.style.display||"none"==b.style.display)&&(b.style.display="block"),j(b),r.use_fallback?b.style.backgroundColor=f.background:(v.push(b),k(b)))}function i(a,b){var c={height:a.clientHeight,width:a.clientWidth};return c.height||c.width?(a.removeAttribute("data-holder-invisible"),c):(a.setAttribute("data-holder-invisible",!0),void b.call(this,a))}function j(b){if(b.holder_data){var c=i(b,a.invisible_error_fn(j));if(c){var d=b.holder_data;d.initial_dimensions=c,d.fluid_data={fluid_height:"%"==d.dimensions.height.slice(-1),fluid_width:"%"==d.dimensions.width.slice(-1),mode:null},d.fluid_data.fluid_width&&!d.fluid_data.fluid_height?(d.fluid_data.mode="width",d.fluid_data.ratio=d.initial_dimensions.width/parseFloat(d.dimensions.height)):!d.fluid_data.fluid_width&&d.fluid_data.fluid_height&&(d.fluid_data.mode="height",d.fluid_data.ratio=parseFloat(d.dimensions.width)/d.initial_dimensions.height)}}}function k(b){var c;c=null==b.nodeType?v:[b];for(var d in c)if(c.hasOwnProperty(d)){var e=c[d];if(e.holder_data){var f=e.holder_data,h=i(e,a.invisible_error_fn(k));if(h){if(f.fluid){if(f.auto)switch(f.fluid_data.mode){case"width":h.height=h.width/f.fluid_data.ratio;break;case"height":h.width=h.height*f.fluid_data.ratio}e.setAttribute("src",g({ctx:w,dimensions:h,template:f.theme,ratio:x,holder:f}))}f.textmode&&"exact"==f.textmode&&(f.exact_dimensions=h,e.setAttribute("src",g({ctx:w,dimensions:f.dimensions,template:f.theme,ratio:x,holder:f})))}}}}function l(b,c){for(var d={theme:o(y.themes.gray,{})},e=!1,f=b.length,g=0;f>g;g++){var h=b[g];a.flags.dimensions.match(h)?(e=!0,d.dimensions=a.flags.dimensions.output(h)):a.flags.fluid.match(h)?(e=!0,d.dimensions=a.flags.fluid.output(h),d.fluid=!0):a.flags.textmode.match(h)?d.textmode=a.flags.textmode.output(h):a.flags.colors.match(h)?d.theme=a.flags.colors.output(h):c.themes[h]?c.themes.hasOwnProperty(h)&&(d.theme=o(c.themes[h],{})):a.flags.font.match(h)?d.font=a.flags.font.output(h):a.flags.auto.match(h)?d.auto=!0:a.flags.text.match(h)&&(d.text=a.flags.text.output(h))}return e?d:!1}function m(a,b){var c="complete",d="readystatechange",e=!1,f=e,g=!0,h=a.document,i=h.documentElement,j=h.addEventListener?"addEventListener":"attachEvent",k=h.addEventListener?"removeEventListener":"detachEvent",l=h.addEventListener?"":"on",m=function(g){(g.type!=d||h.readyState==c)&&(("load"==g.type?a:h)[k](l+g.type,m,e),!f&&(f=!0)&&b.call(a,null))},n=function(){try{i.doScroll("left")}catch(a){return void setTimeout(n,50)}m("poll")};if(h.readyState==c)b.call(a,"lazy");else{if(h.createEventObject&&i.doScroll){try{g=!a.frameElement}catch(o){}g&&n()}h[j](l+"DOMContentLoaded",m,e),h[j](l+d,m,e),a[j](l+"load",m,e)}}function n(a,b){var a=a.match(/^(\W)?(.*)/),b=b||document,c=b["getElement"+(a[1]?"#"==a[1]?"ById":"sByClassName":"sByTagName")],d=c.call(b,a[2]),e=[];return null!==d&&(e=d.length||0===d.length?d:[d]),e}function o(a,b){var c={};for(var d in a)a.hasOwnProperty(d)&&(c[d]=a[d]);for(var d in b)b.hasOwnProperty(d)&&(c[d]=b[d]);return c}var q={use_svg:!1,use_canvas:!1,use_fallback:!1},r={},s=!1;canvas=document.createElement("canvas");var t=1,u=1,v=[];if(canvas.getContext)if(canvas.toDataURL("image/png").indexOf("data:image/png")<0)q.use_fallback=!0;else var w=canvas.getContext("2d");else q.use_fallback=!0;document.createElementNS&&document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect&&(q.use_svg=!0,q.use_canvas=!1),q.use_fallback||(t=window.devicePixelRatio||1,u=w.webkitBackingStorePixelRatio||w.mozBackingStorePixelRatio||w.msBackingStorePixelRatio||w.oBackingStorePixelRatio||w.backingStorePixelRatio||1);var x=t/u,y={domain:"holder.js",images:"img",bgnodes:".holderjs",themes:{gray:{background:"#eee",foreground:"#aaa",size:12},social:{background:"#3a5a97",foreground:"#fff",size:12},industrial:{background:"#434A52",foreground:"#C2F200",size:12},sky:{background:"#0D8FDB",foreground:"#fff",size:12},vine:{background:"#39DBAC",foreground:"#1E292C",size:12},lava:{background:"#F8591A",foreground:"#1C2846",size:12}},stylesheet:""};a.flags={dimensions:{regex:/^(\d+)x(\d+)$/,output:function(a){var b=this.regex.exec(a);return{width:+b[1],height:+b[2]}}},fluid:{regex:/^([0-9%]+)x([0-9%]+)$/,output:function(a){var b=this.regex.exec(a);return{width:b[1],height:b[2]}}},colors:{regex:/#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i,output:function(a){var b=this.regex.exec(a);return{size:y.themes.gray.size,foreground:"#"+b[2],background:"#"+b[1]}}},text:{regex:/text\:(.*)/,output:function(a){return this.regex.exec(a)[1]}},font:{regex:/font\:(.*)/,output:function(a){return this.regex.exec(a)[1]}},auto:{regex:/^auto$/},textmode:{regex:/textmode\:(.*)/,output:function(a){return this.regex.exec(a)[1]}}};var z=function(){if(window.XMLSerializer){var a=new XMLSerializer,b="http://www.w3.org/2000/svg",c=document.createElementNS(b,"svg");c.webkitMatchesSelector&&c.setAttribute("xmlns","http://www.w3.org/2000/svg");var e=document.createElementNS(b,"rect"),f=document.createElementNS(b,"text"),g=document.createTextNode(null);return f.setAttribute("text-anchor","middle"),f.appendChild(g),c.appendChild(e),c.appendChild(f),function(b){return c.setAttribute("width",b.width),c.setAttribute("height",b.height),e.setAttribute("width",b.width),e.setAttribute("height",b.height),e.setAttribute("fill",b.template.background),f.setAttribute("x",b.width/2),f.setAttribute("y",b.height/2),g.nodeValue=b.text,f.setAttribute("style",d({fill:b.template.foreground,"font-weight":"bold","font-size":b.text_height+"px","font-family":b.font,"dominant-baseline":"central"})),a.serializeToString(c)}}}();for(var A in a.flags)a.flags.hasOwnProperty(A)&&(a.flags[A].match=function(a){return a.match(this.regex)});a.invisible_error_fn=function(){return function(a){if(a.hasAttribute("data-holder-invisible"))throw new Error("Holder: invisible placeholder")}},a.add_theme=function(b,c){return null!=b&&null!=c&&(y.themes[b]=c),a},a.add_image=function(b,c){var d=n(c);if(d.length)for(var e=0,f=d.length;f>e;e++){var g=document.createElement("img");g.setAttribute("data-src",b),d[e].appendChild(g)}return a},a.run=function(b){r=o({},q),s=!0;var c=o(y,b),d=[],e=[],f=[];for(null!=c.use_canvas&&c.use_canvas&&(r.use_canvas=!0,r.use_svg=!1),"string"==typeof c.images?e=n(c.images):window.NodeList&&c.images instanceof window.NodeList?e=c.images:window.Node&&c.images instanceof window.Node?e=[c.images]:window.HTMLCollection&&c.images instanceof window.HTMLCollection&&(e=c.images),"string"==typeof c.bgnodes?f=n(c.bgnodes):window.NodeList&&c.elements instanceof window.NodeList?f=c.bgnodes:window.Node&&c.bgnodes instanceof window.Node&&(f=[c.bgnodes]),k=0,j=e.length;j>k;k++)d.push(e[k]);var g=document.getElementById("holderjs-style");g||(g=document.createElement("style"),g.setAttribute("id","holderjs-style"),g.type="text/css",document.getElementsByTagName("head")[0].appendChild(g)),c.nocss||(g.styleSheet?g.styleSheet.cssText+=c.stylesheet:c.stylesheet.length&&g.appendChild(document.createTextNode(c.stylesheet)));for(var i=new RegExp(c.domain+'/(.*?)"?\\)'),j=f.length,k=0;j>k;k++){var m=window.getComputedStyle(f[k],null).getPropertyValue("background-image"),p=m.match(i),t=f[k].getAttribute("data-background-src");if(p){var u=l(p[1].split("/"),c);u&&h("background",f[k],u,m)}else if(null!=t){var u=l(t.substr(t.lastIndexOf(c.domain)+c.domain.length+1).split("/"),c);u&&h("background",f[k],u,m)}}for(j=d.length,k=0;j>k;k++){var v,w;w=v=m=null;try{w=d[k].getAttribute("src"),attr_datasrc=d[k].getAttribute("data-src")}catch(x){}if(null==attr_datasrc&&w&&w.indexOf(c.domain)>=0?m=w:attr_datasrc&&attr_datasrc.indexOf(c.domain)>=0&&(m=attr_datasrc),m){var u=l(m.substr(m.lastIndexOf(c.domain)+c.domain.length+1).split("/"),c);u&&(u.fluid?h("fluid",d[k],u,m):h("image",d[k],u,m))}}return a},m(b,function(){window.addEventListener?(window.addEventListener("resize",k,!1),window.addEventListener("orientationchange",k,!1)):window.attachEvent("onresize",k),s||a.run({}),"object"==typeof window.Turbolinks&&document.addEventListener("page:change",function(){a.run({})})}),"function"==typeof define&&define.amd&&define([],function(){return a}),function(){function a(a){this.message=a}var b="undefined"!=typeof exports?exports:this,c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a.prototype=Error(),a.prototype.name="InvalidCharacterError",b.btoa||(b.btoa=function(b){for(var d,e,f=0,g=c,h="";b.charAt(0|f)||(g="=",f%1);h+=g.charAt(63&d>>8-8*(f%1))){if(e=b.charCodeAt(f+=.75),e>255)throw new a("'btoa' failed");d=d<<8|e}return h}),b.atob||(b.atob=function(b){if(b=b.replace(/=+$/,""),1==b.length%4)throw new a("'atob' failed");for(var d,e,f=0,g=0,h="";e=b.charAt(g++);~e&&(d=f%4?64*d+e:e,f++%4)?h+=String.fromCharCode(255&d>>(6&-2*f)):0)e=c.indexOf(e);return h})}(),document.getElementsByClassName||(document.getElementsByClassName=function(a){var b,c,d,e=document,f=[];if(e.querySelectorAll)return e.querySelectorAll("."+a);if(e.evaluate)for(c=".//*[contains(concat(' ', @class, ' '), ' "+a+" ')]",b=e.evaluate(c,e,null,0,null);d=b.iterateNext();)f.push(d);else for(b=e.getElementsByTagName("*"),c=new RegExp("(^|\\s)"+a+"(\\s|$)"),d=0;d=10}var d,e={bridge:null,version:"0.0.0",disabled:null,outdated:null,ready:null},f={},g=0,h={},i=0,j={},k=null,l=null,m=function(){var a,b,c,d,e="ZeroClipboard.swf";if(document.currentScript&&(d=document.currentScript.src));else{var f=document.getElementsByTagName("script");if("readyState"in f[0])for(a=f.length;a--&&("interactive"!==f[a].readyState||!(d=f[a].src)););else if("loading"===document.readyState)d=f[f.length-1].src;else{for(a=f.length;a--;){if(c=f[a].src,!c){b=null;break}if(c=c.split("#")[0].split("?")[0],c=c.slice(0,c.lastIndexOf("/")+1),null==b)b=c;else if(b!==c){b=null;break}}null!==b&&(d=b)}}return d&&(d=d.split("#")[0].split("?")[0],e=d.slice(0,d.lastIndexOf("/")+1)+e),e}(),n=function(){var a=/\-([a-z])/g,b=function(a,b){return b.toUpperCase()};return function(c){return c.replace(a,b)}}(),o=function(b,c){var d,e,f;return a.getComputedStyle?d=a.getComputedStyle(b,null).getPropertyValue(c):(e=n(c),d=b.currentStyle?b.currentStyle[e]:b.style[e]),"cursor"!==c||d&&"auto"!==d||(f=b.tagName.toLowerCase(),"a"!==f)?d:"pointer"},p=function(b){b||(b=a.event);var c;this!==a?c=this:b.target?c=b.target:b.srcElement&&(c=b.srcElement),K.activate(c)},q=function(a,b,c){a&&1===a.nodeType&&(a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c))},r=function(a,b,c){a&&1===a.nodeType&&(a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c))},s=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)||a.classList.add(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},t=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)&&a.classList.remove(b),a;if(b&&"string"==typeof b||void 0===b){var c=(b||"").split(/\s+/);if(1===a.nodeType&&a.className)if(b){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}else a.className=""}return a},u=function(){var a,b,c,d=1;return"function"==typeof document.body.getBoundingClientRect&&(a=document.body.getBoundingClientRect(),b=a.right-a.left,c=document.body.offsetWidth,d=Math.round(b/c*100)/100),d},v=function(b,c){var d={left:0,top:0,width:0,height:0,zIndex:B(c)-1};if(b.getBoundingClientRect){var e,f,g,h=b.getBoundingClientRect();"pageXOffset"in a&&"pageYOffset"in a?(e=a.pageXOffset,f=a.pageYOffset):(g=u(),e=Math.round(document.documentElement.scrollLeft/g),f=Math.round(document.documentElement.scrollTop/g));var i=document.documentElement.clientLeft||0,j=document.documentElement.clientTop||0;d.left=h.left+e-i,d.top=h.top+f-j,d.width="width"in h?h.width:h.right-h.left,d.height="height"in h?h.height:h.bottom-h.top}return d},w=function(a,b){var c=null==b||b&&b.cacheBust===!0&&b.useNoCache===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+(new Date).getTime():""},x=function(b){var c,d,e,f=[],g=[],h=[];if(b.trustedOrigins&&("string"==typeof b.trustedOrigins?g.push(b.trustedOrigins):"object"==typeof b.trustedOrigins&&"length"in b.trustedOrigins&&(g=g.concat(b.trustedOrigins))),b.trustedDomains&&("string"==typeof b.trustedDomains?g.push(b.trustedDomains):"object"==typeof b.trustedDomains&&"length"in b.trustedDomains&&(g=g.concat(b.trustedDomains))),g.length)for(c=0,d=g.length;d>c;c++)if(g.hasOwnProperty(c)&&g[c]&&"string"==typeof g[c]){if(e=E(g[c]),!e)continue;if("*"===e){h=[e];break}h.push.apply(h,[e,"//"+e,a.location.protocol+"//"+e])}return h.length&&f.push("trustedOrigins="+encodeURIComponent(h.join(","))),"string"==typeof b.jsModuleId&&b.jsModuleId&&f.push("jsModuleId="+encodeURIComponent(b.jsModuleId)),f.join("&")},y=function(a,b,c){if("function"==typeof b.indexOf)return b.indexOf(a,c);var d,e=b.length;for("undefined"==typeof c?c=0:0>c&&(c=e+c),d=c;e>d;d++)if(b.hasOwnProperty(d)&&b[d]===a)return d;return-1},z=function(a){if("string"==typeof a)throw new TypeError("ZeroClipboard doesn't accept query strings.");return a.length?a:[a]},A=function(b,c,d,e){e?a.setTimeout(function(){b.apply(c,d)},0):b.apply(c,d)},B=function(a){var b,c;return a&&("number"==typeof a&&a>0?b=a:"string"==typeof a&&(c=parseInt(a,10))&&!isNaN(c)&&c>0&&(b=c)),b||("number"==typeof N.zIndex&&N.zIndex>0?b=N.zIndex:"string"==typeof N.zIndex&&(c=parseInt(N.zIndex,10))&&!isNaN(c)&&c>0&&(b=c)),b||0},C=function(a,b){if(a&&b!==!1&&"undefined"!=typeof console&&console&&(console.warn||console.log)){var c="`"+a+"` is deprecated. See docs for more info:\n https://github.com/zeroclipboard/zeroclipboard/blob/master/docs/instructions.md#deprecations";console.warn?console.warn(c):console.log(c)}},D=function(){var a,b,c,d,e,f,g=arguments[0]||{};for(a=1,b=arguments.length;b>a;a++)if(null!=(c=arguments[a]))for(d in c)if(c.hasOwnProperty(d)){if(e=g[d],f=c[d],g===f)continue;void 0!==f&&(g[d]=f)}return g},E=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},F=function(){var a=function(a,b){var c,d,e;if(null!=a&&"*"!==b[0]&&("string"==typeof a&&(a=[a]),"object"==typeof a&&"length"in a))for(c=0,d=a.length;d>c;c++)if(a.hasOwnProperty(c)&&(e=E(a[c]))){if("*"===e){b.length=0,b.push("*");break}-1===y(e,b)&&b.push(e)}},b={always:"always",samedomain:"sameDomain",never:"never"};return function(c,d){var e,f=d.allowScriptAccess;if("string"==typeof f&&(e=f.toLowerCase())&&/^always|samedomain|never$/.test(e))return b[e];var g=E(d.moviePath);null===g&&(g=c);var h=[];a(d.trustedOrigins,h),a(d.trustedDomains,h);var i=h.length;if(i>0){if(1===i&&"*"===h[0])return"always";if(-1!==y(c,h))return 1===i&&c===g?"sameDomain":"always"}return"never"}}(),G=function(a){if(null==a)return[];if(Object.keys)return Object.keys(a);var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b},H=function(a){if(a)for(var b in a)a.hasOwnProperty(b)&&delete a[b];return a},I=function(){try{return document.activeElement}catch(a){}return null},J=function(){var a=!1;if("boolean"==typeof e.disabled)a=e.disabled===!1;else{if("function"==typeof ActiveXObject)try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash")&&(a=!0)}catch(b){}!a&&navigator.mimeTypes["application/x-shockwave-flash"]&&(a=!0)}return a},K=function(a,b){return this instanceof K?(this.id=""+g++,h[this.id]={instance:this,elements:[],handlers:{}},a&&this.clip(a),"undefined"!=typeof b&&(C("new ZeroClipboard(elements, options)",N.debug),K.config(b)),this.options=K.config(),"boolean"!=typeof e.disabled&&(e.disabled=!J()),void(e.disabled===!1&&e.outdated!==!0&&null===e.bridge&&(e.outdated=!1,e.ready=!1,O()))):new K(a,b)};K.prototype.setText=function(a){return a&&""!==a&&(f["text/plain"]=a,e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setText?e.bridge.setText(a):e.ready=!1),this},K.prototype.setSize=function(a,b){return e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setSize?e.bridge.setSize(a,b):e.ready=!1,this};var L=function(a){e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setHandCursor?e.bridge.setHandCursor(a):e.ready=!1};K.prototype.destroy=function(){this.unclip(),this.off(),delete h[this.id]};var M=function(){var a,b,c,d=[],e=G(h);for(a=0,b=e.length;b>a;a++)c=h[e[a]].instance,c&&c instanceof K&&d.push(c);return d};K.version="1.3.5";var N={swfPath:m,trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceHandCursor:!1,zIndex:999999999,debug:!0,title:null,autoActivate:!0};K.config=function(a){if("object"==typeof a&&null!==a&&D(N,a),"string"!=typeof a||!a){var b={};for(var c in N)N.hasOwnProperty(c)&&(b[c]="object"==typeof N[c]&&null!==N[c]?"length"in N[c]?N[c].slice(0):D({},N[c]):N[c]);return b}return N.hasOwnProperty(a)?N[a]:void 0},K.destroy=function(){K.deactivate();for(var a in h)if(h.hasOwnProperty(a)&&h[a]){var b=h[a].instance;b&&"function"==typeof b.destroy&&b.destroy()}var c=P(e.bridge);c&&c.parentNode&&(c.parentNode.removeChild(c),e.ready=null,e.bridge=null)},K.activate=function(a){d&&(t(d,N.hoverClass),t(d,N.activeClass)),d=a,s(a,N.hoverClass),Q();var b=N.title||a.getAttribute("title");if(b){var c=P(e.bridge);c&&c.setAttribute("title",b)}var f=N.forceHandCursor===!0||"pointer"===o(a,"cursor");L(f)},K.deactivate=function(){var a=P(e.bridge);a&&(a.style.left="0px",a.style.top="-9999px",a.removeAttribute("title")),d&&(t(d,N.hoverClass),t(d,N.activeClass),d=null)};var O=function(){var b,c,d=document.getElementById("global-zeroclipboard-html-bridge");if(!d){var f=K.config();f.jsModuleId="string"==typeof k&&k||"string"==typeof l&&l||null;var g=F(a.location.host,N),h=x(f),i=N.moviePath+w(N.moviePath,N),j=' ';d=document.createElement("div"),d.id="global-zeroclipboard-html-bridge",d.setAttribute("class","global-zeroclipboard-container"),d.style.position="absolute",d.style.left="0px",d.style.top="-9999px",d.style.width="15px",d.style.height="15px",d.style.zIndex=""+B(N.zIndex),document.body.appendChild(d),d.innerHTML=j}b=document["global-zeroclipboard-flash-bridge"],b&&(c=b.length)&&(b=b[c-1]),e.bridge=b||d.children[0].lastElementChild},P=function(a){for(var b=/^OBJECT|EMBED$/,c=a&&a.parentNode;c&&b.test(c.nodeName)&&c.parentNode;)c=c.parentNode;return c||null},Q=function(){if(d){var a=v(d,N.zIndex),b=P(e.bridge);b&&(b.style.top=a.top+"px",b.style.left=a.left+"px",b.style.width=a.width+"px",b.style.height=a.height+"px",b.style.zIndex=a.zIndex+1),e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setSize?e.bridge.setSize(a.width,a.height):e.ready=!1}return this};K.prototype.on=function(a,b){var c,d,f,g={},i=h[this.id]&&h[this.id].handlers;if("string"==typeof a&&a)f=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)a.hasOwnProperty(c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.on(c,a[c]);if(f&&f.length){for(c=0,d=f.length;d>c;c++)a=f[c].replace(/^on/,""),g[a]=!0,i[a]||(i[a]=[]),i[a].push(b);g.noflash&&e.disabled&&T.call(this,"noflash",{}),g.wrongflash&&e.outdated&&T.call(this,"wrongflash",{flashVersion:e.version}),g.load&&e.ready&&T.call(this,"load",{flashVersion:e.version})}return this},K.prototype.off=function(a,b){var c,d,e,f,g,i=h[this.id]&&h[this.id].handlers;if(0===arguments.length)f=G(i);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)a.hasOwnProperty(c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=i[a],g&&g.length)if(b)for(e=y(b,g);-1!==e;)g.splice(e,1),e=y(b,g,e);else i[a].length=0;return this},K.prototype.handlers=function(a){var b,c=null,d=h[this.id]&&h[this.id].handlers;if(d){if("string"==typeof a&&a)return d[a]?d[a].slice(0):null;c={};for(b in d)d.hasOwnProperty(b)&&d[b]&&(c[b]=d[b].slice(0))}return c};var R=function(b,c,d,e){var f=h[this.id]&&h[this.id].handlers[b];if(f&&f.length){var g,i,j,k=c||this;for(g=0,i=f.length;i>g;g++)j=f[g],c=k,"string"==typeof j&&"function"==typeof a[j]&&(j=a[j]),"object"==typeof j&&j&&"function"==typeof j.handleEvent&&(c=j,j=j.handleEvent),"function"==typeof j&&A(j,c,d,e)}return this};K.prototype.clip=function(a){a=z(a);for(var b=0;bd;d++)f=h[c[d]].instance,f&&f instanceof K&&g.push(f);return g};N.hoverClass="zeroclipboard-is-hover",N.activeClass="zeroclipboard-is-active",N.trustedOrigins=null,N.allowScriptAccess=null,N.useNoCache=!0,N.moviePath="ZeroClipboard.swf",K.detectFlashSupport=function(){return C("ZeroClipboard.detectFlashSupport",N.debug),J()},K.dispatch=function(a,b){if("string"==typeof a&&a){var c=a.toLowerCase().replace(/^on/,"");if(c)for(var e=d&&N.autoActivate===!0?S(d):M(),f=0,g=e.length;g>f;f++)T.call(e[f],c,b)}},K.prototype.setHandCursor=function(a){return C("ZeroClipboard.prototype.setHandCursor",N.debug),a="boolean"==typeof a?a:!!a,L(a),N.forceHandCursor=a,this},K.prototype.reposition=function(){return C("ZeroClipboard.prototype.reposition",N.debug),Q()},K.prototype.receiveEvent=function(a,b){if(C("ZeroClipboard.prototype.receiveEvent",N.debug),"string"==typeof a&&a){var c=a.toLowerCase().replace(/^on/,"");c&&T.call(this,c,b)}},K.prototype.setCurrent=function(a){return C("ZeroClipboard.prototype.setCurrent",N.debug),K.activate(a),this},K.prototype.resetBridge=function(){return C("ZeroClipboard.prototype.resetBridge",N.debug),K.deactivate(),this},K.prototype.setTitle=function(a){if(C("ZeroClipboard.prototype.setTitle",N.debug),a=a||N.title||d&&d.getAttribute("title")){var b=P(e.bridge);b&&b.setAttribute("title",a)}return this},K.setDefaults=function(a){C("ZeroClipboard.setDefaults",N.debug),K.config(a)},K.prototype.addEventListener=function(a,b){return C("ZeroClipboard.prototype.addEventListener",N.debug),this.on(a,b)},K.prototype.removeEventListener=function(a,b){return C("ZeroClipboard.prototype.removeEventListener",N.debug),this.off(a,b)},K.prototype.ready=function(){return C("ZeroClipboard.prototype.ready",N.debug),e.ready===!0};var T=function(a,g){a=a.toLowerCase().replace(/^on/,"");var h=g&&g.flashVersion&&b(g.flashVersion)||null,i=d,j=!0;switch(a){case"load":if(h){if(!c(h))return void T.call(this,"onWrongFlash",{flashVersion:h});e.outdated=!1,e.ready=!0,e.version=h}break;case"wrongflash":h&&!c(h)&&(e.outdated=!0,e.ready=!1,e.version=h);break;case"mouseover":s(i,N.hoverClass);break;case"mouseout":N.autoActivate===!0&&K.deactivate();break;case"mousedown":s(i,N.activeClass);break;case"mouseup":t(i,N.activeClass);break;case"datarequested":if(i){var k=i.getAttribute("data-clipboard-target"),l=k?document.getElementById(k):null;if(l){var m=l.value||l.textContent||l.innerText;m&&this.setText(m)}else{var n=i.getAttribute("data-clipboard-text");n&&this.setText(n)}}j=!1;break;case"complete":H(f),i&&i!==I()&&i.focus&&i.focus()}var o=i,p=[this,g];return R.call(this,a,o,p,j)};"function"==typeof define&&define.amd?define(["require","exports","module"],function(a,b,c){return k=c&&c.id||null,K}):"object"==typeof module&&module&&"object"==typeof module.exports&&module.exports&&"function"==typeof a.require?(l=module.id||null,module.exports=K):a.ZeroClipboard=K}(function(){return this}()),/*! 19 | * JavaScript for Bootstrap's docs (http://getbootstrap.com) 20 | * Copyright 2011-2014 Twitter, Inc. 21 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 22 | * details, see http://creativecommons.org/licenses/by/3.0/. 23 | */ 24 | !function(a){"use strict";a(function(){var b=a(window),c=a(document.body);c.scrollspy({target:".bs-docs-sidebar"}),b.on("load",function(){c.scrollspy("refresh")}),a(".bs-docs-container [href=#]").click(function(a){a.preventDefault()}),setTimeout(function(){var b=a(".bs-docs-sidebar");b.affix({offset:{top:function(){var c=b.offset().top,d=parseInt(b.children(0).css("margin-top"),10),e=a(".bs-docs-nav").height();return this.top=c-e-d},bottom:function(){return this.bottom=a(".bs-docs-footer").outerHeight(!0)}}})},100),setTimeout(function(){a(".bs-top").affix()},100),function(){var b=a("#bs-theme-stylesheet"),c=a(".bs-docs-theme-toggle");c.click(function(){var a=b.attr("href");a&&0!==a.indexOf("data")?(b.attr("href",""),c.text("Preview theme")):(b.attr("href",b.attr("data-href")),c.text("Disable theme preview"))})}(),a(".tooltip-demo").tooltip({selector:'[data-toggle="tooltip"]',container:"body"}),a(".popover-demo").popover({selector:'[data-toggle="popover"]',container:"body"}),a(".tooltip-test").tooltip(),a(".popover-test").popover(),a(".bs-docs-popover").popover(),a(".bs-docs-popover-dismiss").popover({trigger:"focus"}),a("#loading-example-btn").click(function(){var b=a(this);b.button("loading"),setTimeout(function(){b.button("reset")},3e3)}),ZeroClipboard.config({moviePath:"/assets/flash/ZeroClipboard.swf",hoverClass:"btn-clipboard-hover"}),a(".highlight").each(function(){var b=a(this),c=b.prev(),d='
Copy
';c.hasClass("bs-example")?c.before(d.replace(/btn-clipboard/,"btn-clipboard with-example")):b.before(d)});var d=new ZeroClipboard(a(".btn-clipboard")),e=a("#global-zeroclipboard-html-bridge");d.on("load",function(){e.data("placement","top").attr("title","Copy to clipboard").tooltip()}),d.on("dataRequested",function(b){var c=a(this).parent().nextAll(".highlight").first();b.setText(c.text())}),d.on("complete",function(){e.attr("title","Copied!").tooltip("fixTitle").tooltip("show").attr("title","Copy to clipboard").tooltip("fixTitle")}),d.on("noflash wrongflash",function(){e.attr("title","Flash required").tooltip("fixTitle").tooltip("show")})})}(jQuery); -------------------------------------------------------------------------------- /static/assets/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); --------------------------------------------------------------------------------