Compare commits
14 Commits
Author | SHA1 | Date |
---|---|---|
Felipe Martín | 26ec4814e4 | |
Felipe Martín | a83eb4a58e | |
Felipe Martín | d73e57bff6 | |
Felipe Martín | 4f5505ceba | |
Felipe Martín | 80634771ac | |
Felipe Martín | 4b685bb51c | |
Felipe Martín | 311f79a155 | |
Felipe Martín | e9f2304008 | |
Felipe Martín | 5ca6115ed5 | |
Felipe Martín | bba9109c82 | |
Felipe Martín | 1c3eebb5ca | |
Felipe Martín | 16c6cbceb6 | |
Felipe Martín | 79dd3339d1 | |
Felipe Martín | 97329994f6 |
4
.bowerrc
4
.bowerrc
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"directory": "fmartingrcom/themes/v1/static/bower"
|
||||
}
|
||||
"directory": "fmartingrcom/static/bower_components"
|
||||
}
|
|
@ -1,12 +1,22 @@
|
|||
# Python
|
||||
*.pyc
|
||||
__pycache__
|
||||
.virtualenv
|
||||
|
||||
# App
|
||||
local_settings.py
|
||||
|
||||
# Emacs
|
||||
*~
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
*.sqlite3
|
||||
node_modules
|
||||
**/bower
|
||||
|
||||
# utilities
|
||||
bower_components
|
||||
**/CACHE/*
|
||||
*.sublime-workspace
|
||||
.sass-cache
|
||||
/projects
|
||||
/fmartingrcom/media
|
||||
/fmartingrcom/themes/*/static/bower
|
||||
/fmartingrcom/themes/*/static/bower_components
|
||||
/node_modules
|
||||
|
||||
# css
|
||||
/fmartingrcom/themes/*/static/css
|
11
README.md
11
README.md
|
@ -1,6 +1,11 @@
|
|||
fmartingr.com
|
||||
===
|
||||
=============
|
||||
|
||||
My personal site codebase.
|
||||
## Setup
|
||||
|
||||
http://fmartingr.com
|
||||
```
|
||||
virtualenv -p python3.5 .virtualenv
|
||||
source .virtualenv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
```
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
fmartingr.com
|
||||
===
|
||||
|
||||
My personal site codebase.
|
||||
|
||||
http://fmartingr.com
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "fmartingr.com",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/fmartingr/fmartingr.com",
|
||||
"authors": [
|
||||
"Felipe Martin <fmartingr@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"font-awesome": "~4.2.0",
|
||||
"google-code-prettify": "~1.0.3",
|
||||
"jquery": "~2.1.3",
|
||||
"lightbox2": "~2.7.1"
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.views.generic import View as DjangoView
|
||||
|
||||
|
||||
|
@ -7,6 +6,5 @@ class View(DjangoView):
|
|||
data = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.data = {}
|
||||
self.data['section'] = self.section
|
||||
return super(View, self).__init__(*args, **kwargs)
|
|
@ -0,0 +1,59 @@
|
|||
from django.contrib import admin
|
||||
from .models import Entry, Tag
|
||||
from django.utils.translation import ugettext as _
|
||||
import reversion
|
||||
|
||||
|
||||
#
|
||||
# ENTRY
|
||||
#
|
||||
class EntryAdmin(reversion.VersionAdmin):
|
||||
list_display = ('title', 'date', 'status', 'tag_list', 'preview_link')
|
||||
list_display_links = ('title', )
|
||||
|
||||
list_filter = ('date', 'draft', )
|
||||
search_fields = ('title', 'content', )
|
||||
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
|
||||
suit_form_tabs = (
|
||||
('general', _('General')),
|
||||
('content', _('Content')),
|
||||
)
|
||||
|
||||
fieldsets = [
|
||||
(None, {
|
||||
'classes': ('suit-tab suit-tab-general',),
|
||||
'fields': ('title', 'slug', 'draft', 'date', 'tags', )
|
||||
}),
|
||||
(None, {
|
||||
'classes': ('suit-tab suit-tab-content full-width',),
|
||||
'fields': ('content', )
|
||||
}),
|
||||
]
|
||||
|
||||
def preview_link(self, obj):
|
||||
return '<a href="%s">View »</a>' % (
|
||||
obj.get_absolute_url()
|
||||
)
|
||||
preview_link.allow_tags = True
|
||||
|
||||
def tag_list(self, obj):
|
||||
return ", ".join([x.name for x in obj.tags.all()])
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not change:
|
||||
obj.author = request.user
|
||||
super(self.__class__, self).save_model(request, obj, form, change)
|
||||
|
||||
|
||||
admin.site.register(Entry, EntryAdmin)
|
||||
|
||||
|
||||
#
|
||||
# TAG
|
||||
#
|
||||
class TagAdmin(reversion.VersionAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(Tag, TagAdmin)
|
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
#import ckeditor.fields
|
||||
import ckeditor.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -19,7 +19,7 @@ class Migration(migrations.Migration):
|
|||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('title', models.CharField(max_length=128)),
|
||||
('date', models.DateTimeField()),
|
||||
('content', models.TextField()),
|
||||
('content', ckeditor.fields.RichTextField()),
|
||||
('slug', models.SlugField(max_length=128)),
|
||||
('draft', models.BooleanField(default=True)),
|
||||
('author', models.ForeignKey(related_name='author', editable=False, to=settings.AUTH_USER_MODEL)),
|
|
@ -0,0 +1,69 @@
|
|||
from django.db import models
|
||||
from django.conf import settings
|
||||
from datetime import datetime
|
||||
from django.utils.timezone import utc
|
||||
from ckeditor.fields import RichTextField
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import activate
|
||||
|
||||
|
||||
#
|
||||
# ENTRY
|
||||
#
|
||||
class Entry(models.Model):
|
||||
title = models.CharField(max_length=128)
|
||||
date = models.DateTimeField()
|
||||
content = RichTextField()
|
||||
slug = models.SlugField(max_length=128)
|
||||
draft = models.BooleanField(default=True)
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
editable=False,
|
||||
related_name='author'
|
||||
)
|
||||
tags = models.ManyToManyField('Tag', null=True, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def status(self):
|
||||
status = 'Published'
|
||||
|
||||
if self.date > datetime.now(tz=utc):
|
||||
status = 'Scheduled'
|
||||
|
||||
if self.draft:
|
||||
status = 'Draft'
|
||||
|
||||
return status
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {
|
||||
'year': self.date.year,
|
||||
'month': self.date.strftime("%m"),
|
||||
'day': self.date.strftime("%d"),
|
||||
'slug': self.slug
|
||||
}
|
||||
url = reverse('blog:item', kwargs=kwargs)
|
||||
|
||||
return url
|
||||
|
||||
class Meta:
|
||||
app_label = 'blog'
|
||||
ordering = ['-date']
|
||||
verbose_name_plural = 'Entries'
|
||||
|
||||
|
||||
#
|
||||
# TAG
|
||||
#
|
||||
class Tag(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
color = models.CharField(max_length=6, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
app_label = 'blog'
|
||||
ordering = ['name']
|
|
@ -0,0 +1,38 @@
|
|||
from django.conf.urls import patterns, url
|
||||
|
||||
from .views import ListView, EntryView, SearchView, RSSView
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
None,
|
||||
# Post list with page
|
||||
url(
|
||||
r'^page/(?P<page_number>\d+)/$',
|
||||
ListView.as_view(),
|
||||
name='list'
|
||||
),
|
||||
# Post list
|
||||
url(
|
||||
r'^$',
|
||||
ListView.as_view(),
|
||||
name='list'
|
||||
),
|
||||
# Single entry
|
||||
url(
|
||||
r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[\w\-]+)/$',
|
||||
EntryView.as_view(),
|
||||
name='item'
|
||||
),
|
||||
# RSS
|
||||
url(
|
||||
r'^rss\.xml$',
|
||||
RSSView.as_view(),
|
||||
name='rss'
|
||||
),
|
||||
# Search
|
||||
url(
|
||||
r'^search/$',
|
||||
SearchView.as_view(),
|
||||
name='search',
|
||||
)
|
||||
)
|
|
@ -1,6 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
from django.core.paginator import Paginator
|
||||
from django.utils import translation
|
||||
from django.conf import settings
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Q
|
||||
|
||||
|
@ -18,8 +20,8 @@ def get_posts(query=None, limit=None):
|
|||
)
|
||||
if query and len(query) > 0:
|
||||
items = items.filter(
|
||||
Q(title__icontains=query) |
|
||||
Q(content__icontains=query) |
|
||||
Q(title__icontains=query) | \
|
||||
Q(content__icontains=query) | \
|
||||
Q(tags__name__iexact=query)
|
||||
).distinct()
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template.loader import get_template
|
||||
from django.template import RequestContext
|
||||
from django.http import Http404, HttpResponseRedirect, HttpResponse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
|
||||
import fmartingrcom.apps.blog.utils as blog_utils
|
||||
from .models import Entry
|
||||
from fmartingrcom.apps._core.views import View
|
||||
from fmartingrcom.apps.config.models import SiteConfiguration
|
||||
|
||||
|
||||
config = SiteConfiguration.objects.get()
|
||||
|
||||
|
||||
class ListView(View):
|
||||
section = 'blog'
|
||||
template = 'blog/list.jinja'
|
||||
|
||||
def get(self, request, page_number=1):
|
||||
if 'page' in request.GET:
|
||||
page_number = int(request.GET['page'])
|
||||
|
||||
paginator, page = blog_utils.get_paginator(request, page_number)
|
||||
|
||||
self.data['page'] = page
|
||||
self.data['page_number'] = page_number
|
||||
self.data['paginator'] = paginator
|
||||
|
||||
context = RequestContext(request, self.data)
|
||||
return render_to_response(self.template, context_instance=context)
|
||||
|
||||
|
||||
class EntryView(View):
|
||||
section = 'blog'
|
||||
template = 'blog/entry.jinja'
|
||||
|
||||
def get(self, request, year, month, day, slug):
|
||||
try:
|
||||
filters = {
|
||||
'slug': slug,
|
||||
'date__year': int(year),
|
||||
'date__month': int(month),
|
||||
'date__day': int(day),
|
||||
}
|
||||
|
||||
item = Entry.objects.get(**filters)
|
||||
except Entry.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
paginator, page = blog_utils.get_paginator(request, item=item)
|
||||
|
||||
self.data['page'] = page
|
||||
self.data['paginator'] = paginator
|
||||
self.data['item'] = item
|
||||
|
||||
context = RequestContext(request, self.data)
|
||||
return render_to_response(self.template, context_instance=context)
|
||||
|
||||
|
||||
class SearchView(ListView):
|
||||
template = 'blog/search.jinja'
|
||||
|
||||
def post(self, request):
|
||||
page_number = 1
|
||||
if 'page' in request.GET:
|
||||
page_number = int(request.GET['page'])
|
||||
|
||||
search_query = request.POST['query']
|
||||
|
||||
if not search_query:
|
||||
return HttpResponseRedirect(reverse('blog:list'))
|
||||
|
||||
paginator, page = blog_utils.get_paginator(
|
||||
request, page_number, query=search_query
|
||||
)
|
||||
|
||||
self.data['page'] = page
|
||||
self.data['page_number'] = page_number
|
||||
self.data['paginator'] = paginator
|
||||
self.data['search_query'] = search_query
|
||||
|
||||
context = RequestContext(request, self.data)
|
||||
return render_to_response(self.template, context_instance=context)
|
||||
|
||||
|
||||
class RSSView(View):
|
||||
template = 'blog/rss.jinja'
|
||||
|
||||
def get(self, request):
|
||||
limit = config.rss_items
|
||||
items = blog_utils.get_posts(limit=limit)
|
||||
self.data['items'] = items
|
||||
|
||||
context = RequestContext(request, self.data)
|
||||
template = get_template(self.template)
|
||||
return HttpResponse(
|
||||
template.render(context),
|
||||
content_type='text/xml'
|
||||
)
|
|
@ -1,12 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.conf.urls import url
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .views import HomepageView
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
urlpatterns = patterns(
|
||||
None,
|
||||
url(
|
||||
r'^$',
|
||||
HomepageView.as_view(),
|
||||
name='homepage'
|
||||
),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from fmartingrcom.apps._core.views import View
|
||||
|
||||
|
||||
class HomepageView(View):
|
||||
template = 'homepage.jinja'
|
||||
section = 'homepage'
|
||||
|
||||
def get(self, request):
|
||||
context = RequestContext(request, self.data)
|
||||
return render_to_response(self.template, context_instance=context)
|
|
@ -4,7 +4,7 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# 3rd party
|
||||
from reversion.admin import VersionAdmin
|
||||
import reversion
|
||||
|
||||
# app
|
||||
from . import models
|
||||
|
@ -13,7 +13,7 @@ from . import models
|
|||
#
|
||||
# Group
|
||||
#
|
||||
class GroupAdmin(VersionAdmin):
|
||||
class GroupAdmin(reversion.VersionAdmin):
|
||||
list_display = ('name', 'order', )
|
||||
list_display_links = ('name', )
|
||||
list_editable = ('order', )
|
||||
|
@ -36,17 +36,11 @@ class ProjectImageInline(admin.TabularInline):
|
|||
model = models.ProjectImage
|
||||
|
||||
|
||||
class ProjectAdmin(VersionAdmin):
|
||||
class ProjectAdmin(reversion.VersionAdmin):
|
||||
list_display = ('title', 'date', 'group', 'company', 'role', 'visible', )
|
||||
list_editable = ('visible', )
|
||||
inlines = (ProjectImageInline, )
|
||||
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
|
||||
fieldsets = [
|
||||
('General', {
|
||||
'fields': ('group', ('title', 'slug'), ('company', 'role'), 'date', ('stack', 'url', 'visible'), 'description')
|
||||
})
|
||||
]
|
||||
|
||||
admin.site.register(models.Project, ProjectAdmin)
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import models, migrations
|
||||
import fmartingrcom.apps.projects.models
|
||||
import ckeditor.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -32,7 +33,7 @@ class Migration(migrations.Migration):
|
|||
('date', models.DateTimeField()),
|
||||
('company', models.CharField(max_length=128, null=True, blank=True)),
|
||||
('role', models.CharField(max_length=128)),
|
||||
('description', models.TextField()),
|
||||
('description', ckeditor.fields.RichTextField()),
|
||||
('visible', models.BooleanField(default=True)),
|
||||
('stack', models.CharField(default=None, max_length=256, null=True, blank=True)),
|
||||
('url', models.CharField(default=None, max_length=256, null=True, blank=True)),
|
|
@ -1,12 +1,16 @@
|
|||
# coding: utf-8
|
||||
|
||||
# python
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
# django
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
# 3rd party
|
||||
#from ckeditor.fields import RichTextField
|
||||
from ckeditor.fields import RichTextField
|
||||
|
||||
|
||||
#
|
||||
|
@ -28,7 +32,7 @@ class Project(models.Model):
|
|||
date = models.DateTimeField()
|
||||
company = models.CharField(max_length=128, null=True, blank=True)
|
||||
role = models.CharField(max_length=128)
|
||||
description = models.TextField()
|
||||
description = RichTextField()
|
||||
visible = models.BooleanField(default=True)
|
||||
stack = models.CharField(max_length=256, null=True, blank=True,
|
||||
default=None)
|
|
@ -1,13 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.conf.urls import url
|
||||
# coding: utf-8
|
||||
|
||||
# django
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
# app
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
urlpatterns = patterns(
|
||||
None,
|
||||
# Project list
|
||||
url(
|
||||
r'^$',
|
||||
views.ListView.as_view(),
|
||||
name='list'
|
||||
),
|
||||
]
|
||||
)
|
|
@ -1,7 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.shortcuts import render
|
||||
# coding: utf-8
|
||||
|
||||
# django
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
|
||||
# project
|
||||
from fmartingrcom.apps._core.views import View
|
||||
from fmartingrcom.apps.config.models import SiteConfiguration
|
||||
|
||||
# app
|
||||
from . import models
|
||||
|
||||
config = SiteConfiguration.objects.get()
|
||||
|
@ -18,4 +25,5 @@ class ListView(View):
|
|||
'groups': models.Group.objects.all().order_by('order'),
|
||||
}
|
||||
|
||||
return render(request, self.template, data)
|
||||
context = RequestContext(request, data)
|
||||
return render_to_response(self.template, context_instance=context)
|
|
@ -15,13 +15,16 @@ SECRET_KEY = '0123456789'
|
|||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
#
|
||||
# APPLICATIONS
|
||||
#
|
||||
INSTALLED_APPS = (
|
||||
#'suit',
|
||||
'suit',
|
||||
'solo',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
|
@ -128,14 +131,12 @@ THUMBNAIL_ALIASES = {
|
|||
# MEDIA FILES
|
||||
#
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
|
||||
CKEDITOR_UPLOAD_PATH = 'ckeditor/'
|
||||
|
||||
CKEDITOR_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar': 'Advanced',
|
||||
'allowedContent': True,
|
||||
'toolbar': 'Standard',
|
||||
'width': '100%',
|
||||
'stylesSet': (
|
||||
{
|
||||
|
@ -165,61 +166,44 @@ CKEDITOR_CONFIGS = {
|
|||
#
|
||||
# TEMPLATES
|
||||
#
|
||||
TEMPLATE_PATHS = ('{}/themes/v1/templates/'.format(BASE_DIR), )
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django_jinja.backend.Jinja2",
|
||||
"DIRS": TEMPLATE_PATHS,
|
||||
"OPTIONS": {
|
||||
"match_extension": ".jinja",
|
||||
"context_processors": (
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.core.context_processors.debug",
|
||||
"django.core.context_processors.i18n",
|
||||
"django.core.context_processors.media",
|
||||
"django.core.context_processors.static",
|
||||
"django.core.context_processors.tz",
|
||||
"django.core.context_processors.request",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"fmartingrcom.apps.config.context_processors.config"
|
||||
),
|
||||
"extensions": (
|
||||
"jinja2.ext.do",
|
||||
"jinja2.ext.loopcontrols",
|
||||
"jinja2.ext.with_",
|
||||
"jinja2.ext.i18n",
|
||||
"jinja2.ext.autoescape",
|
||||
"django_jinja.builtins.extensions.CsrfExtension",
|
||||
"django_jinja.builtins.extensions.CacheExtension",
|
||||
"django_jinja.builtins.extensions.TimezoneExtension",
|
||||
"django_jinja.builtins.extensions.UrlsExtension",
|
||||
"django_jinja.builtins.extensions.StaticFilesExtension",
|
||||
"django_jinja.builtins.extensions.DjangoFiltersExtension",
|
||||
"django_jinja.builtins.extensions.DjangoExtraFiltersExtension",
|
||||
"compressor.contrib.jinja2ext.CompressorExtension",
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'APP_DIRS': True,
|
||||
'DIRS': (),
|
||||
'OPTIONS': {
|
||||
"context_processors": (
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.core.context_processors.debug",
|
||||
"django.core.context_processors.i18n",
|
||||
"django.core.context_processors.media",
|
||||
"django.core.context_processors.static",
|
||||
"django.core.context_processors.tz",
|
||||
"django.core.context_processors.request",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"fmartingrcom.apps.config.context_processors.config"
|
||||
)
|
||||
},
|
||||
},
|
||||
TEMPLATE_LOADERS = (
|
||||
'django_jinja.loaders.AppLoader',
|
||||
'django_jinja.loaders.FileSystemLoader',
|
||||
)
|
||||
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
'{}/themes/v1/templates/'.format(BASE_DIR),
|
||||
)
|
||||
|
||||
JINJA2_EXTENSIONS = [
|
||||
"jinja2.ext.do",
|
||||
"jinja2.ext.loopcontrols",
|
||||
"jinja2.ext.with_",
|
||||
"jinja2.ext.i18n",
|
||||
"jinja2.ext.autoescape",
|
||||
"django_jinja.builtins.extensions.CsrfExtension",
|
||||
"django_jinja.builtins.extensions.CacheExtension",
|
||||
"django_jinja.builtins.extensions.TimezoneExtension",
|
||||
"django_jinja.builtins.extensions.UrlsExtension",
|
||||
"django_jinja.builtins.extensions.StaticFilesExtension",
|
||||
"django_jinja.builtins.extensions.DjangoFiltersExtension",
|
||||
"django_jinja.builtins.extensions.DjangoExtraFiltersExtension",
|
||||
"compressor.contrib.jinja2ext.CompressorExtension",
|
||||
]
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.core.context_processors.debug",
|
||||
"django.core.context_processors.i18n",
|
||||
"django.core.context_processors.media",
|
||||
"django.core.context_processors.static",
|
||||
"django.core.context_processors.tz",
|
||||
"django.core.context_processors.request",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"fmartingrcom.apps.config.context_processors.config"
|
||||
)
|
||||
|
||||
#
|
||||
# ADMIN
|
||||
#
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
@ -31,13 +31,9 @@ article.blog-entry
|
|||
.info
|
||||
|
||||
.content
|
||||
font-size: 1.2em
|
||||
line-height: 150%
|
||||
line-height: 140%
|
||||
padding-top: 15px
|
||||
|
||||
&.liveeditor
|
||||
outline: 0
|
||||
|
||||
img
|
||||
box-shadow: $sidebar-bg 0 0 4px
|
||||
max-width: 100%
|
||||
|
@ -58,7 +54,7 @@ article.blog-entry
|
|||
> :last-child
|
||||
margin-bottom: 0
|
||||
|
||||
p code, li code, td code
|
||||
code
|
||||
display: inline-block
|
||||
white-space: no-wrap
|
||||
background: #fff
|
||||
|
@ -74,10 +70,6 @@ article.blog-entry
|
|||
padding: 0 .3em
|
||||
margin: -1px 0
|
||||
|
||||
pre code
|
||||
font-size: .8em
|
||||
line-height: 1.2em
|
||||
|
||||
hr
|
||||
width: 50%
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
body
|
||||
&.homepage
|
||||
background-color: #fff
|
||||
background-image: url('/static/images/homepage/bg.png')
|
||||
background-attachment: fixed
|
||||
|
||||
section.content
|
||||
// http://www.sitepoint.com/css3-shuffled-paper/
|
||||
.papers
|
||||
background: #fff
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3)
|
||||
margin: 30px 0
|
||||
//max-width: 700px
|
||||
max-width: 600px
|
||||
padding: 24px
|
||||
position: relative
|
||||
font-size: 110%
|
||||
width: 80%
|
||||
|
||||
.papers:before, .papers:after
|
||||
content: ""
|
||||
height: 98%
|
||||
position: absolute
|
||||
width: 100%
|
||||
z-index: -1
|
||||
|
||||
.papers:before
|
||||
background: #fafafa
|
||||
box-shadow: 0 0 8px rgba(0,0,0,0.2)
|
||||
left: -5px
|
||||
top: 4px
|
||||
@include rotate(-2.5)
|
||||
|
||||
.papers:after
|
||||
background: #f6f6f6
|
||||
box-shadow: 0 0 3px rgba(0,0,0,0.2)
|
||||
right: -3px
|
||||
top: 1px
|
||||
@include rotate(1.4)
|
||||
|
||||
.picture
|
||||
background-color: white
|
||||
padding: 8px
|
||||
padding-bottom: 30px
|
||||
margin-left: 40px
|
||||
margin-bottom: 12px
|
||||
box-shadow: 0 0 3px rgba(0,0,0,0.2)
|
||||
@include rotate(10)
|
||||
margin-top: -10px
|
||||
margin-right: -50px
|
||||
|
||||
.img-circle
|
||||
border-radius: 50%
|
||||
|
||||
.social
|
||||
$button-size: 50px
|
||||
height: $button-size + 4px
|
||||
|
||||
.button
|
||||
border-radius: 50%
|
||||
display: inline-block
|
||||
font-size: 120%
|
||||
height: $button-size
|
||||
line-height: $button-size
|
||||
margin-right: 5px
|
||||
text-align: center
|
||||
width: $button-size
|
||||
|
||||
&:hover
|
||||
border-bottom: 0
|
||||
|
||||
&.email
|
||||
background-color: $email-color
|
||||
color: $email-text-color
|
||||
box-shadow: 0 0 1px $email-color
|
||||
|
||||
&:hover
|
||||
background-color: darken($email-color, 12%)
|
||||
|
||||
&.twitter
|
||||
background-color: $twitter-color
|
||||
color: $twitter-text-color
|
||||
box-shadow: 0 0 1px $twitter-color
|
||||
|
||||
&:hover
|
||||
background-color: darken($twitter-color, 12%)
|
||||
|
||||
&.github
|
||||
background-color: $github-color
|
||||
color: $github-text-color
|
||||
box-shadow: 0 0 1px $github-color
|
||||
|
||||
&:hover
|
||||
background-color: darken($github-color, 12%)
|
||||
|
||||
&.linkedin
|
||||
background-color: $linkedin-color
|
||||
color: $linkedin-text-color
|
||||
box-shadow: 0 0 1px $linkedin-color
|
||||
|
||||
&:hover
|
||||
background-color: darken($linkedin-color, 12%)
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
pre .str, code .str
|
||||
color: #65b042
|
||||
|
||||
pre .kwd, code .kwd
|
||||
color: #e28964
|
||||
|
||||
pre .com, code .com
|
||||
color: #aeaeae
|
||||
font-style: italic
|
||||
|
||||
pre .typ, code .typ
|
||||
color: #89bdff
|
||||
|
||||
pre .lit, code .lit
|
||||
color: #3387cc
|
||||
|
||||
pre .pun, code .pun, pre .pln, code .pln
|
||||
color: #fff
|
||||
|
||||
pre .tag, code .tag
|
||||
color: #89bdff
|
||||
|
||||
pre .atn, code .atn
|
||||
color: #bdb76b
|
||||
|
||||
pre .atv, code .atv
|
||||
color: #65b042
|
||||
|
||||
pre .dec, code .dec
|
||||
color: #3387cc
|
||||
|
||||
pre.prettyprint, code.prettyprint
|
||||
background-color: #242424
|
||||
border: 0 !important
|
||||
-moz-border-radius: 0
|
||||
-webkit-border-radius: 0
|
||||
-o-border-radius: 0
|
||||
-ms-border-radius: 0
|
||||
-khtml-border-radius: 0
|
||||
border-radius: 0
|
||||
|
||||
pre.prettyprint
|
||||
font-size: 84%
|
||||
line-height: 120%
|
||||
width: auto
|
||||
margin: 1em auto
|
||||
padding: 12px !important
|
||||
white-space: pre-wrap
|
||||
font-size: 86%
|
||||
|
||||
ol.linenums
|
||||
margin-top: 0
|
||||
margin-bottom: 0
|
||||
color: #aeaeae
|
||||
|
||||
li
|
||||
&.L0, &.L1, &.L2, &.L3, &.L5, &.L6, &.L7, &.L8
|
||||
list-style-type: none
|
||||
|
||||
@media print
|
||||
pre .str, code .str
|
||||
color: #060
|
||||
pre .kwd, code .kwd
|
||||
color: #006
|
||||
font-weight: bold
|
||||
pre .com, code .com
|
||||
color: #600
|
||||
font-style: italic
|
||||
pre .typ, code .typ
|
||||
color: #404
|
||||
font-weight: bold
|
||||
pre .lit, code .lit
|
||||
color: #044
|
||||
pre .pun, code .pun
|
||||
color: #440
|
||||
pre .pln, code .pln
|
||||
color: #000
|
||||
pre .tag, code .tag
|
||||
color: #006
|
||||
font-weight: bold
|
||||
pre .atn, code .atn
|
||||
color: #404
|
||||
pre .atv, code .atv
|
||||
color: #060
|
|
@ -0,0 +1,60 @@
|
|||
// General
|
||||
$text-color: #242424
|
||||
$font-family: "Georgia", "Open Sans", OpenSansRegular, sans-serif
|
||||
$font-size: 18px
|
||||
|
||||
$anchor-text-color: #2277bb
|
||||
$anchor-text-shadow-color: #004b6b
|
||||
|
||||
$headers-font-family: 'Antic Slab', serif
|
||||
|
||||
$text-shadow-color: #fff
|
||||
$text-shadow-properties: 1px 1px 1px
|
||||
|
||||
$strong-text-color: #3e4349
|
||||
|
||||
$box-shadow-properties: 0px 0px 5px
|
||||
|
||||
// Sidebar
|
||||
$sidebar-bg: #242424
|
||||
$sidebar-text-color: #fff
|
||||
$sidebar-width: 260px
|
||||
|
||||
$footer-padding: 10px
|
||||
|
||||
// Content
|
||||
$content-sidebar-gap: 40px
|
||||
|
||||
// Buttons
|
||||
$twitter-color: #55acee
|
||||
$twitter-text-color: #fff
|
||||
|
||||
$email-color: #db4437
|
||||
$email-text-color: #e7e6dd
|
||||
|
||||
$github-color: #999
|
||||
$github-text-color: #000
|
||||
|
||||
$linkedin-color: #0976b4
|
||||
$linkedin-text-color: #fff
|
||||
|
||||
$rss-color: #ff9557
|
||||
$rss-text-color: #eee
|
||||
|
||||
$blog-color: desaturate(#fe160e, 50)
|
||||
$blog-text-color: #fff
|
||||
|
||||
$homepage-color: desaturate(#00cbf4, 50)
|
||||
$homepage-text-color: #efefef
|
||||
|
||||
$projects-color: desaturate(#491b93, 50)
|
||||
$projects-text-color: #efefef
|
||||
|
||||
// Colors
|
||||
$warning-color: #f39c12
|
||||
$warning-text-color: #fff
|
||||
|
||||
// font
|
||||
@font-face
|
||||
font-family: fmartingr
|
||||
src: url('/static/fmartingr.ttf')
|
|
@ -5,5 +5,6 @@
|
|||
@import "homepage"
|
||||
@import "blog"
|
||||
@import "projects"
|
||||
@import "syntax"
|
||||
|
||||
@import "responsive"
|
|
@ -4,10 +4,9 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400|Antic+Slab" rel="stylesheet" type="text/css">
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" href="{{ static('bower/font-awesome/css/font-awesome.css') }}" type="text/css" />
|
||||
<link rel="stylesheet" href="{{ static('bower/pure/buttons.css') }}" />
|
||||
<link rel="stylesheet" href="{{ static('sass/style.sass') }}" type="text/x-sass" />
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}bower/font-awesome/css/font-awesome.css" type="text/css" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}sass/style.sass" type="text/x-sass" />
|
||||
{% endblock %}
|
||||
{% endcompress %}
|
||||
{% block head %}{% endblock %}
|
||||
|
@ -67,7 +66,7 @@
|
|||
</section>
|
||||
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{{ static('js/mobile.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}js/mobile.js"></script>
|
||||
{% block javascript %}{% endblock %}
|
||||
{% endcompress %}
|
||||
{% if config.google_analytics %}
|
|
@ -0,0 +1,53 @@
|
|||
{% extends "blog/layout.jinja" %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ super() }} | {{ item.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block submenu %}
|
||||
{% if item.status() == 'Published' %}
|
||||
{% if page.number > 1 %}
|
||||
<a href="{{ url('blog:list', page.number) }}" class="button prev-page pull-left gap">
|
||||
{% else %}
|
||||
<a href="{{ url('blog:list') }}" class="button prev-page pull-left gap">
|
||||
{% endif %}
|
||||
<i class="fa fa-angle-double-left"></i> Go back
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article class="blog-entry {% if item.draft %}draft{% endif %}">
|
||||
<h1 class="entry-title">
|
||||
<a class="dark" href="#">{{ item.title }}</a>
|
||||
</h1>
|
||||
|
||||
<div class="info text-left">
|
||||
<i class="fa fa-calendar"></i>
|
||||
<time datetime="{{ item.date }}"
|
||||
pubdate=""
|
||||
data-updated="true">{{ item.date|dt('%B %e, %Y') }}</time>
|
||||
{% if config.disqus_shortname and config.enable_comments %}
|
||||
|
||||
<i class="fa fa-comment"></i> <a href="{{ item.get_absolute_url() }}#disqus_thread">0 Comments</a>
|
||||
{% endif %}
|
||||
{% if item.tags.count() > 0 %}
|
||||
|
||||
<i class="fa fa-tag"></i> {{ item.tags.all()|join(', ') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="content">{{ item.content|safe }}</div>
|
||||
<div class="clearfix"></div>
|
||||
{% if config.show_share_buttons %}
|
||||
<div class="share">
|
||||
<a href="https://twitter.com/share" class="twitter-share-button" data-text="{{ item.title }} - {{ config.base_url }}{{ item.get_absolute_url() }}" data-via="{{ config.twitter_username }}" data-size="large">Tweet</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
{% endif %}
|
||||
{% if config.disqus_shortname and config.enable_comments %}
|
||||
<hr class="big" />
|
||||
<div class="comments" id="disqus_thread"></div>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
|
@ -0,0 +1,38 @@
|
|||
{% extends "_layout.jinja" %}
|
||||
|
||||
{% block page_title %}Blog{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" href="{{ static('bower/google-code-prettify/bin/prettify.min.css') }}" />
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script type="text/javascript" src="{{ static('bower/google-code-prettify/bin/run_prettify.min.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
<hr />
|
||||
|
||||
{% block submenu %}
|
||||
<form action="{{ url('blog:search') }}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="text" class="search-field" name="query" placeholder="Search in blog..."
|
||||
{% if search_query %}
|
||||
value=""
|
||||
{% endif %}
|
||||
/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
<a href="{{ url('blog:rss') }}" class="button rss">
|
||||
<i class="fa fa-rss"></i> RSS
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block endbody %}
|
||||
{{ super() }}
|
||||
{% if config.disqus_shortname and config.enable_comments %}
|
||||
{% from '_macros.jinja' import disqus with context %}
|
||||
{{ disqus() }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -65,17 +65,17 @@
|
|||
|
||||
<div class="pagination">
|
||||
{% if page.has_next() %}
|
||||
<a href="{{ url('blog:list') }}?page={{ page.next_page_number() }}" class="button prev-page pull-right gap half">
|
||||
<a href="{{ url('blog:list', page.next_page_number()) }}" class="button prev-page pull-right gap half">
|
||||
Older <i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
<a href="#">o
|
||||
<a href="#">
|
||||
{% endif %}
|
||||
|
||||
{% if page.has_previous() %}
|
||||
{% if page.previous_page_number() == 1 %}
|
||||
<a href="{{ url('blog:list') }}" class="button prev-page pull-left gap half">
|
||||
{% else %}
|
||||
<a href="{{ url('blog:list') }}?page={{ page.previous_page_number() }}" class="button prev-page pull-left gap half">
|
||||
<a href="{{ url('blog:list', page.previous_page_number()) }}" class="button prev-page pull-left gap half">
|
||||
{% endif %}
|
||||
<i class="fa fa-angle-double-left"></i> Newer
|
||||
</a>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<ul>
|
||||
{% for p in range(1, paginator.num_pages+1) %}
|
||||
<li {% if p == page_number %}class="active"{% endif %}>
|
||||
<a href="{{ url('blog:list') }}">?page={{ p }}</a>
|
||||
<a href="{{ url('blog:list', p) }}">{{ p }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "_layout.jinja" %}
|
||||
|
||||
{% block menu %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="papers">
|
||||
<div class="picture pull-right hide-mobile">
|
||||
<img src="http://cdn.fmartingr.com/avatar.png" width="200" />
|
||||
</div>
|
||||
<p>Hi! I'm Felipe, and I am a developer.</p>
|
||||
|
||||
<p>I have been playing with code for a while now, but I also enjoy geeking around with computers, algorithms and other stuff; the less I know about it, the better! Learning new things every day is my way of life.</p>
|
||||
|
||||
<p>I think that developers are like artists, writers and composers... we all make art. But we don't use strokes and colors, words or notes, we make it through code. And I really love it.</p>
|
||||
|
||||
<p>If you want to get in touch, feel free to drop me a line.</p>
|
||||
|
||||
<div class="social">
|
||||
<a href="//es.linkedin.com/in/felipemartingarcia" class="button linkedin"><span class="fa fa-linkedin"></span></a>
|
||||
<a href="//twitter.com/fmartingr" class="button twitter"><span class="fa fa-twitter"></span></a>
|
||||
<a href="//github.com/fmartingr" class="button github"><span class="fa fa-github"></span></a>
|
||||
<a href="mailto:me@fmartingr.com" class="button email"><span class="fa fa-envelope"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls import include, url
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
|
||||
from .apps.homepage.sitemap import HomeSitemap
|
||||
|
@ -13,14 +13,15 @@ sitemaps = {
|
|||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
#(r'^ckeditor/', include('ckeditor.urls')),
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
(r'^ckeditor/', include('ckeditor.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^blog/', include('fmartingrcom.apps.blog.urls', namespace='blog')),
|
||||
url(r'^portfolio/',
|
||||
include('fmartingrcom.apps.projects.urls', namespace='projects')),
|
||||
url(r'^', include('fmartingrcom.apps.homepage.urls', namespace='home')),
|
||||
]
|
||||
url(r'^$', include('fmartingrcom.apps.homepage.urls', namespace='home')),
|
||||
)
|
||||
|
||||
|
||||
from django.conf import settings
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
|
||||
"fmartingrcom.settings.base")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "fmartingr.com",
|
||||
"version": "0.1.0",
|
||||
"description": "fmartingr.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fmartingr/fmartingr.com.git"
|
||||
},
|
||||
"author": "Felipe Martin <fmartingr@me.com>",
|
||||
"license": "GPLv2",
|
||||
"bugs": {
|
||||
"url": "https://github.com/fmartingr/fmartingr.com/issues"
|
||||
},
|
||||
"homepage": "https://github.com/fmartingr/fmartingr.com",
|
||||
"devDependencies": {
|
||||
"grunt": ">=0.4.1",
|
||||
"grunt-contrib-watch": ">=0.4.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
Django==1.7.7
|
||||
|
||||
Jinja2==2.7.3
|
||||
django-jinja==1.3.1
|
||||
|
||||
dj-database-url==0.3.0
|
||||
|
||||
django-suit==0.2.12
|
||||
django-reversion==1.8.5
|
||||
django-solo==1.1.0
|
||||
django-ckeditor-updated==4.4.4
|
||||
|
||||
pytz==2014.10
|
||||
|
||||
django-compressor==1.4
|
||||
|
||||
easy-thumbnails==2.2
|
22
bower.json
22
bower.json
|
@ -1,25 +1,23 @@
|
|||
{
|
||||
"name": "fmartingr.com",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/fmartingr/fmartingr.com",
|
||||
"name": "fmartingrcom",
|
||||
"description": "fmartingr.com",
|
||||
"main": "gulpfile.js",
|
||||
"authors": [
|
||||
"Felipe Martin <fmartingr@me.com>"
|
||||
"Felipe Martin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"license": "GPLv2",
|
||||
"moduleType": [],
|
||||
"homepage": "",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"fmartingrcom/static/bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"font-awesome": "~4.2.0",
|
||||
"google-code-prettify": "~1.0.3",
|
||||
"jquery": "~2.1.3",
|
||||
"lightbox2": "~2.7.1",
|
||||
"pen": "^0.2.2",
|
||||
"pure": "^0.6.0",
|
||||
"highlight": "^9.2.0"
|
||||
"google-code-prettify": "~1.0.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ from fabric.api import *
|
|||
from fabric.context_managers import settings, hide
|
||||
from fabric.decorators import with_settings
|
||||
from fabric.contrib.files import exists
|
||||
from fabric.colors import yellow, red, white, green, blue # blue is used
|
||||
from fabric.colors import yellow, red, white, green
|
||||
|
||||
|
||||
#
|
||||
|
@ -230,7 +230,11 @@ def deploy():
|
|||
with cd(release_dir):
|
||||
with prefix('source {}/virtualenv/bin/activate'.format(release_dir)):
|
||||
with prefix('export APP_CONFIGFILE="{}"'.format(app_configfile)):
|
||||
cmd = 'python code/manage.py migrate {} --no-input'\
|
||||
cmd = 'python code/manage.py syncdb {}'.format(managepy_affix)
|
||||
subheader(cmd)
|
||||
run(cmd)
|
||||
|
||||
cmd = 'python code/manage.py migrate {} --no-initial-data'\
|
||||
.format(managepy_affix)
|
||||
subheader(cmd)
|
||||
run(cmd)
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
import importlib
|
||||
|
||||
from flask import Flask, make_response, render_template, url_for
|
||||
from flask_admin import Admin
|
||||
from flask_admin.contrib.sqla import ModelView
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
from . import conf
|
||||
|
||||
|
||||
def get_theme_folder(folder):
|
||||
return 'themes/{}/{}'.format(conf.THEME, folder)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.debug = conf.DEBUG
|
||||
app.secret_key = conf.SECRET_KEY
|
||||
app.static_folder = get_theme_folder(conf.STATIC_FOLDER)
|
||||
app.template_folder = get_theme_folder(conf.TEMPLATE_FOLDER)
|
||||
app.config['cdn_domain'] = conf.CDN_DOMAIN
|
||||
|
||||
# Database
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = conf.DATABASE_PATH
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
# Enable admin if set in the conf
|
||||
if conf.ENABLE_ADMIN:
|
||||
admin = Admin(app, name='fmartingrcom', template_mode='bootstrap3')
|
||||
|
||||
# Method to register admin models
|
||||
def register_admin_model(model):
|
||||
admin.add_view(ModelView(model, db.session))
|
||||
|
||||
# Autoload enabled blueprints
|
||||
for blueprint in conf.BLUEPRINTS:
|
||||
module = importlib.import_module(
|
||||
'.apps.{}'.format(blueprint),
|
||||
package=__name__)
|
||||
app.register_blueprint(getattr(module, blueprint))
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_server_error(error):
|
||||
app.logger.error(error)
|
||||
return make_response(render_template('errors/500.html'), 500)
|
||||
|
||||
@app.context_processor
|
||||
def global_context():
|
||||
return {'current_year': datetime.now().strftime('%Y') }
|
||||
|
||||
# Patch url_for to use a CDN_DOMAIN if needed
|
||||
def patch_url_for(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
result = func(*args, **kwargs)
|
||||
cdn_domain = app.config.get('cdn_domain', None)
|
||||
|
||||
try:
|
||||
rewrite_path = result.split('/')[1] in conf.CDN_PATHS
|
||||
except IndexError:
|
||||
rewrite_path = False
|
||||
|
||||
if cdn_domain and rewrite_path:
|
||||
return '{}{}'.format(cdn_domain, result)
|
||||
return result
|
||||
return wrapper
|
||||
app.jinja_env.globals['url_for'] = patch_url_for(url_for)
|
|
@ -0,0 +1 @@
|
|||
from . import home, blog, portfolio
|
|
@ -0,0 +1,55 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from flask import Blueprint, render_template, request, abort, url_for
|
||||
from sqlalchemy import Column, String, Text, Integer, Boolean, DateTime
|
||||
from fmartingrcom import db, register_admin_model, conf
|
||||
|
||||
|
||||
blog = Blueprint('blog', __name__)
|
||||
|
||||
|
||||
# Models
|
||||
class Post(db.Model):
|
||||
__tablename__ = 'blog_post'
|
||||
id = Column(Integer, primary_key=True)
|
||||
title = Column(String(250))
|
||||
slug = Column(String(250), index=True)
|
||||
date = Column(DateTime(True), index=True)
|
||||
content = Column(Text)
|
||||
html = Column(Text)
|
||||
draft = Column(Boolean, default=True)
|
||||
|
||||
@property
|
||||
def absolute_url(self):
|
||||
return url_for('blog.blog_post',
|
||||
year=self.date.year,
|
||||
month=str(self.date.month).zfill(2),
|
||||
day=str(self.date.day).zfill(2),
|
||||
slug=self.slug)
|
||||
|
||||
if conf.ENABLE_ADMIN:
|
||||
register_admin_model(Post)
|
||||
|
||||
|
||||
# Views
|
||||
@blog.route('/blog/<int:year>/<string:month>/<string:day>/<slug>/')
|
||||
def blog_post(year, month, day, slug):
|
||||
item = Post.query.filter_by(slug=slug).first_or_404()
|
||||
context = {
|
||||
'item': item,
|
||||
}
|
||||
return render_template('blog/post.html', **context)
|
||||
|
||||
@blog.route('/blog/')
|
||||
def blog_list():
|
||||
try:
|
||||
page_num = int(request.args.get('page', 1))
|
||||
except ValueError:
|
||||
page_num = 1
|
||||
|
||||
query = Post.query.order_by(Post.date)
|
||||
paginator = query.paginate(page_num, 1)
|
||||
context = {
|
||||
'items': paginator.items,
|
||||
'paginator': paginator
|
||||
}
|
||||
return render_template('blog/list.html', **context)
|
|
@ -1,104 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from .models import Entry, Tag, Attachment
|
||||
from ckeditor.widgets import CKEditorWidget
|
||||
from django.utils.translation import ugettext as _
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
|
||||
from django import forms
|
||||
|
||||
|
||||
class EntryAdminForm(forms.ModelForm):
|
||||
content = forms.CharField(widget=CKEditorWidget())
|
||||
|
||||
class Meta:
|
||||
model = Entry
|
||||
fields = ('title', 'slug', 'draft', 'date', 'tags', 'content')
|
||||
|
||||
|
||||
#
|
||||
# ATTACHMENT
|
||||
#
|
||||
class AttachmentAdmin(VersionAdmin):
|
||||
list_display = ('filename', 'sha1', )
|
||||
fields = ('file', )
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
handler = form.cleaned_data.get('file')
|
||||
Attachment.upload(handler, handler.name)
|
||||
|
||||
|
||||
class EntryAttachmentInlineAdmin(admin.StackedInline):
|
||||
model = Entry.attachments.through
|
||||
|
||||
admin.site.register(Attachment, AttachmentAdmin)
|
||||
|
||||
|
||||
#
|
||||
# ENTRY
|
||||
#
|
||||
class EntryAdmin(VersionAdmin):
|
||||
form = EntryAdminForm
|
||||
|
||||
list_display = ('title', 'date', 'status', 'tag_list', 'preview_link')
|
||||
list_display_links = ('title', )
|
||||
list_filter = ('date', 'draft', )
|
||||
search_fields = ('title', 'content', 'markdown', )
|
||||
|
||||
filter_horizontal = ('tags',)
|
||||
|
||||
actions_on_top = True
|
||||
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
|
||||
ignore_duplicate_revisions = True
|
||||
|
||||
suit_form_tabs = (
|
||||
('general', _('General')),
|
||||
('content', _('Content')),
|
||||
)
|
||||
|
||||
inlines = (
|
||||
EntryAttachmentInlineAdmin,
|
||||
)
|
||||
|
||||
fieldsets = [
|
||||
('General', {
|
||||
'classes': ('suit-tab suit-tab-general collapse',),
|
||||
'fields': (('title', 'slug', 'draft'), ('date', 'tags'), )
|
||||
}),
|
||||
('Content', {
|
||||
'classes': ('suit-tab suit-tab-content full-width wide',),
|
||||
'fields': ('content', )
|
||||
}),
|
||||
('Markdown', {
|
||||
'classes': ('suit-tab suit-tab-content full-width collapse',),
|
||||
'fields': ('markdown', )
|
||||
})
|
||||
]
|
||||
|
||||
def preview_link(self, obj):
|
||||
return '<a href="%s">View »</a>' % (
|
||||
obj.get_absolute_url()
|
||||
)
|
||||
preview_link.allow_tags = True
|
||||
|
||||
def tag_list(self, obj):
|
||||
return ", ".join([x.name for x in obj.tags.all()])
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not change:
|
||||
obj.author = request.user
|
||||
super(self.__class__, self).save_model(request, obj, form, change)
|
||||
|
||||
|
||||
admin.site.register(Entry, EntryAdmin)
|
||||
|
||||
|
||||
#
|
||||
# TAG
|
||||
#
|
||||
class TagAdmin(VersionAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(Tag, TagAdmin)
|
|
@ -1,25 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-03-13 09:51
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='entry',
|
||||
name='markdown',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entry',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, to='blog.Tag'),
|
||||
),
|
||||
]
|
|
@ -1,31 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-03-21 19:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import fmartingrcom.apps.blog.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0002_auto_20160313_1051'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Attachment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('filename', models.CharField(max_length=256)),
|
||||
('sha1', models.CharField(max_length=40)),
|
||||
('mimetype', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('file', models.FileField(upload_to=fmartingrcom.apps.blog.models.attachment_upload_to)),
|
||||
('creation_date', models.DateTimeField(auto_now_add=True)),
|
||||
('modification_date', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Attachment',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-03-21 21:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0003_attachment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='entry',
|
||||
name='attachments',
|
||||
field=models.ManyToManyField(blank=True, to='blog.Attachment'),
|
||||
),
|
||||
]
|
|
@ -1,197 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.utils.timezone import utc
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.dispatch.dispatcher import receiver
|
||||
from django.db.models.signals import pre_delete
|
||||
|
||||
from fmartingrcom.utils import sha1_checksum
|
||||
|
||||
import mistune
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
#
|
||||
# ENTRY
|
||||
#
|
||||
class Entry(models.Model):
|
||||
title = models.CharField(max_length=128)
|
||||
date = models.DateTimeField()
|
||||
content = models.TextField()
|
||||
markdown = models.TextField(blank=True)
|
||||
slug = models.SlugField(max_length=128)
|
||||
draft = models.BooleanField(default=True)
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
editable=False,
|
||||
related_name='author'
|
||||
)
|
||||
tags = models.ManyToManyField('Tag', blank=True)
|
||||
attachments = models.ManyToManyField('Attachment', blank=True,
|
||||
related_name='entries')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def status(self):
|
||||
status = 'Published'
|
||||
|
||||
if self.date > datetime.now(tz=utc):
|
||||
status = 'Scheduled'
|
||||
|
||||
if self.draft:
|
||||
status = 'Draft'
|
||||
|
||||
return status
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {
|
||||
'year': self.date.year,
|
||||
'month': self.date.strftime("%m"),
|
||||
'day': self.date.strftime("%d"),
|
||||
'slug': self.slug
|
||||
}
|
||||
url = reverse('blog:item', kwargs=kwargs)
|
||||
|
||||
return url
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.markdown:
|
||||
content = mistune.markdown(self.markdown)
|
||||
self.content = content
|
||||
result = super(Entry, self).save(*args, **kwargs)
|
||||
return result
|
||||
|
||||
class Meta:
|
||||
app_label = 'blog'
|
||||
ordering = ['-date']
|
||||
verbose_name_plural = 'Entries'
|
||||
|
||||
|
||||
#
|
||||
# TAG
|
||||
#
|
||||
class Tag(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
color = models.CharField(max_length=6, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
app_label = 'blog'
|
||||
ordering = ['name']
|
||||
|
||||
|
||||
#
|
||||
# ATTACHMENT
|
||||
#
|
||||
def attachment_upload_to(instance, filename):
|
||||
return os.path.join('{}_.{}'.format(
|
||||
instance.media_path, instance.extension
|
||||
))
|
||||
|
||||
|
||||
class Attachment(models.Model):
|
||||
filename = models.CharField(max_length=256)
|
||||
sha1 = models.CharField(max_length=40)
|
||||
mimetype = models.CharField(max_length=256, blank=True, null=True)
|
||||
file = models.FileField(upload_to=attachment_upload_to)
|
||||
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
modification_date = models.DateTimeField(auto_now=True)
|
||||
|
||||
@property
|
||||
def extension(self):
|
||||
return self.file.url.split('.')[-1]
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self.file.url
|
||||
|
||||
@property
|
||||
def media_path(self):
|
||||
p1 = self.sha1[0:2]
|
||||
p2 = self.sha1[2:4]
|
||||
return 'attachment/{}/{}/{}/'.format(p1, p2, self.sha1)
|
||||
|
||||
@staticmethod
|
||||
def upload(handler, filename=None):
|
||||
"""
|
||||
Given a certain file handler returns an `Image` objects
|
||||
handler can be:
|
||||
+ django.core.files.File instance __or subclass of__ (UploadedFile)
|
||||
+ string with an absolute path to an image
|
||||
"""
|
||||
FLAGS = 'rw+'
|
||||
|
||||
# File Path
|
||||
if isinstance(handler, str):
|
||||
f = File(open(handler, FLAGS))
|
||||
elif issubclass(handler.__class__, File) or isinstance(handler, File):
|
||||
# Re-read with correct flags
|
||||
f = handler
|
||||
f.mode = FLAGS
|
||||
else:
|
||||
# I give up!
|
||||
f = handler
|
||||
|
||||
if not filename:
|
||||
filename = f.name.split('/')[-1]
|
||||
|
||||
sha1 = sha1_checksum(f)
|
||||
|
||||
# Check if file exists
|
||||
try:
|
||||
obj = Attachment.objects.get(sha1=sha1)
|
||||
except Attachment.DoesNotExist:
|
||||
obj = Attachment()
|
||||
obj.file = f
|
||||
obj.sha1 = sha1
|
||||
obj.filename = filename
|
||||
obj.mimetype = Attachment.get_mimetype(f, obj.filename)
|
||||
obj.save()
|
||||
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_mimetype(handler, filename):
|
||||
re_mime_validate = re.compile('\w+/\w+(; \w+=[^;]+)*')
|
||||
|
||||
# subprocess
|
||||
p = subprocess.Popen(('file', '--brief', '--mime-type', '-'),
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate(handler.read())
|
||||
if re_mime_validate.match(stdout):
|
||||
mimetype = stdout.split()[0]
|
||||
else:
|
||||
# by python
|
||||
mime, encoding = mimetypes.guess_type(filename)
|
||||
mimetype = mime if mime else None
|
||||
|
||||
# rewind file descriptor
|
||||
handler.file.seek(0)
|
||||
|
||||
return mimetype
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Attachment'
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Attachment)
|
||||
def clean_files_on_delete(sender, instance, **kwargs):
|
||||
try:
|
||||
shutil.rmtree('/'.join(instance.file.path.split('/')[:-1]))
|
||||
except Exception as error:
|
||||
logger.warning(error, exc_info=True)
|
|
@ -1,52 +0,0 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from .views import (
|
||||
ListView, AttachmentView,
|
||||
EntryView, EntryAttachmentView, EntryLiveEditView,
|
||||
SearchView, RSSView)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# Global attachment URL
|
||||
url(
|
||||
r'^attachment/(?P<attachment_id>\d+)$',
|
||||
AttachmentView.as_view(),
|
||||
name='attachment'
|
||||
),
|
||||
# Post list
|
||||
url(
|
||||
r'^$',
|
||||
ListView.as_view(),
|
||||
name='list'
|
||||
),
|
||||
# Single entry
|
||||
url(
|
||||
r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[\w\-]+)/$',
|
||||
EntryView.as_view(),
|
||||
name='item'
|
||||
),
|
||||
# Live edit entry
|
||||
url(
|
||||
r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[\w\-]+)/edit/$',
|
||||
EntryLiveEditView.as_view(),
|
||||
name='item-liveedit'
|
||||
),
|
||||
# Attachment
|
||||
url(
|
||||
r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[\w\-]+)/attachment/(?P<filename>.*)$',
|
||||
EntryAttachmentView.as_view(),
|
||||
name='item-attachment'
|
||||
),
|
||||
# RSS
|
||||
url(
|
||||
r'^rss\.xml$',
|
||||
RSSView.as_view(),
|
||||
name='rss'
|
||||
),
|
||||
# Search
|
||||
url(
|
||||
r'^search/$',
|
||||
SearchView.as_view(),
|
||||
name='search',
|
||||
)
|
||||
]
|
|
@ -1,149 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import Http404, HttpResponseRedirect, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
import fmartingrcom.apps.blog.utils as blog_utils
|
||||
from .models import Entry, Attachment
|
||||
from fmartingrcom.apps._core.views import View
|
||||
from fmartingrcom.apps.config.models import SiteConfiguration
|
||||
|
||||
|
||||
config = SiteConfiguration.objects.get()
|
||||
|
||||
|
||||
class AttachmentView(View):
|
||||
def get(self, request, attachment_id):
|
||||
try:
|
||||
attachment = Attachment.objects.get(pk=int(attachment_id))
|
||||
return HttpResponseRedirect(attachment.url)
|
||||
except Attachment.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
|
||||
class ListView(View):
|
||||
section = 'blog'
|
||||
template = 'blog/list.jinja'
|
||||
|
||||
def get(self, request):
|
||||
page_number = int(request.GET.get('page', 1))
|
||||
|
||||
paginator, page = blog_utils.get_paginator(request, page_number)
|
||||
|
||||
self.data['page'] = page
|
||||
self.data['page_number'] = page_number
|
||||
self.data['paginator'] = paginator
|
||||
|
||||
return render(request, self.template, self.data)
|
||||
|
||||
|
||||
class EntryView(View):
|
||||
section = 'blog'
|
||||
template = 'blog/entry.jinja'
|
||||
|
||||
def get_object(self, year, month, day, slug):
|
||||
try:
|
||||
filters = {
|
||||
'slug': slug,
|
||||
'date__year': int(year),
|
||||
'date__month': int(month),
|
||||
'date__day': int(day),
|
||||
}
|
||||
|
||||
return Entry.objects.get(**filters)
|
||||
except Entry.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
def get(self, request, year, month, day, slug):
|
||||
item = self.get_object(year, month, day, slug)
|
||||
paginator, page = blog_utils.get_paginator(request, item=item)
|
||||
|
||||
self.data['page'] = page
|
||||
self.data['paginator'] = paginator
|
||||
self.data['item'] = item
|
||||
|
||||
return render(request, self.template, self.data)
|
||||
|
||||
|
||||
class EntryAttachmentView(EntryView):
|
||||
def get(self, request, year, month, day, slug, filename):
|
||||
item = self.get_object(year, month, day, slug)
|
||||
attachment = item.attachments.get(filename=filename)
|
||||
if attachment:
|
||||
return HttpResponseRedirect(attachment.url)
|
||||
raise Http404
|
||||
|
||||
|
||||
class EntryLiveEditView(View):
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(EntryLiveEditView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def post(self, request, year, month, day, slug):
|
||||
if not request.user.is_superuser:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
try:
|
||||
filters = {
|
||||
'slug': slug,
|
||||
'date__year': int(year),
|
||||
'date__month': int(month),
|
||||
'date__day': int(day),
|
||||
}
|
||||
|
||||
item = Entry.objects.get(**filters)
|
||||
except Entry.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
data = json.loads(request.body)
|
||||
if 'markdown' in data:
|
||||
item.markdown = data['markdown']
|
||||
item.save()
|
||||
return HttpResponse(status=200)
|
||||
|
||||
if 'content' in data:
|
||||
item.content = data['content']
|
||||
item.save()
|
||||
return HttpResponse(status=200)
|
||||
return HttpResponse(status=204)
|
||||
|
||||
|
||||
class SearchView(ListView):
|
||||
template = 'blog/search.jinja'
|
||||
|
||||
def post(self, request):
|
||||
page_number = 1
|
||||
if 'page' in request.GET:
|
||||
page_number = int(request.GET['page'])
|
||||
|
||||
search_query = request.POST['query']
|
||||
|
||||
if not search_query:
|
||||
return HttpResponseRedirect(reverse('blog:list'))
|
||||
|
||||
paginator, page = blog_utils.get_paginator(
|
||||
request, page_number, query=search_query
|
||||
)
|
||||
|
||||
self.data['page'] = page
|
||||
self.data['page_number'] = page_number
|
||||
self.data['paginator'] = paginator
|
||||
self.data['search_query'] = search_query
|
||||
|
||||
return render(request, self.template, self.data)
|
||||
|
||||
|
||||
class RSSView(View):
|
||||
template = 'blog/rss.jinja'
|
||||
|
||||
def get(self, request):
|
||||
limit = config.rss_items
|
||||
items = blog_utils.get_posts(limit=limit)
|
||||
self.data['items'] = items
|
||||
|
||||
return render(request, self.template, self.data,
|
||||
content_type='text/xml')
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flask import Blueprint, render_template
|
||||
|
||||
|
||||
home = Blueprint('home', __name__)
|
||||
|
||||
|
||||
@home.route('/')
|
||||
def homepage():
|
||||
return render_template('home.html')
|
|
@ -1,11 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.shortcuts import render
|
||||
from fmartingrcom.apps._core.views import View
|
||||
|
||||
|
||||
class HomepageView(View):
|
||||
template = 'homepage.jinja'
|
||||
section = 'homepage'
|
||||
|
||||
def get(self, request):
|
||||
return render(request, self.template, self.data)
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flask import Blueprint, render_template
|
||||
|
||||
|
||||
portfolio = Blueprint('portfolio', __name__)
|
||||
|
||||
@portfolio.route('/projects/')
|
||||
def portfolio_list():
|
||||
context = {}
|
||||
return render_template('portfolio.html', **context)
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
PROJECT_PATH = os.getcwd()
|
||||
|
||||
# Enables or disables debug mode
|
||||
DEBUG = False
|
||||
|
||||
# Secret key for some stuff
|
||||
SECRET_KEY = '0123456789'
|
||||
|
||||
# Database URI
|
||||
DATABASE_PATH = '/tmp/fmartingr.db'
|
||||
|
||||
# Admin
|
||||
ENABLE_ADMIN = False
|
||||
|
||||
# Static and media files
|
||||
THEME = 'v2'
|
||||
CDN_DOMAIN = None
|
||||
STATIC_FOLDER = 'static'
|
||||
TEMPLATE_FOLDER = 'templates'
|
||||
CDN_PATHS = (STATIC_FOLDER, )
|
||||
|
||||
# Enabled blueprints
|
||||
BLUEPRINTS = (
|
||||
'home',
|
||||
'blog',
|
||||
'portfolio',
|
||||
)
|
||||
|
||||
# Try to import local_settings module for this enviroment
|
||||
try:
|
||||
from local_settings import *
|
||||
except ImportError:
|
||||
pass
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
Model = declarative_base()
|
|
@ -1,158 +0,0 @@
|
|||
{% extends "blog/layout.jinja" %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ super() }} | {{ item.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block submenu %}
|
||||
{% if item.status() == 'Published' %}
|
||||
{% if page.number > 1 %}
|
||||
<a href="{{ url('blog:list') }}?page={{ page.number }}" class="button prev-page pull-left gap">
|
||||
{% else %}
|
||||
<a href="{{ url('blog:list') }}" class="button prev-page pull-left gap">
|
||||
{% endif %}
|
||||
<i class="fa fa-angle-double-left"></i> Go back
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article class="blog-entry {% if item.draft %}draft{% endif %}"
|
||||
{% if request.user.is_superuser %}data-liveedit-url="{{ item.get_absolute_url() }}edit/"{% endif %}>
|
||||
<h1 class="entry-title">
|
||||
<a class="dark" href="#">{{ item.title }}</a>
|
||||
</h1>
|
||||
|
||||
<div class="info text-left">
|
||||
{% if request.user.is_superuser %}
|
||||
<div class="pull-right">
|
||||
<a class="pure-button button-small button-secondary" data-button="live-edit">Live edit</a>
|
||||
<a class="pure-button button-small" href="{{ url('admin:blog_entry_change', item.pk) }}">Edit in admin</a>
|
||||
<a class="pure-button hidden" data-button="save">Save</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<i class="fa fa-calendar"></i>
|
||||
<time datetime="{{ item.date }}"
|
||||
pubdate=""
|
||||
data-updated="true">{{ item.date|dt('%B %e, %Y') }}</time>
|
||||
{% if config.disqus_shortname and config.enable_comments %}
|
||||
|
||||
<i class="fa fa-comment"></i> <a href="{{ item.get_absolute_url() }}#disqus_thread">0 Comments</a>
|
||||
{% endif %}
|
||||
{% if item.tags.count() > 0 %}
|
||||
|
||||
<i class="fa fa-tag"></i> {{ item.tags.all()|join(', ') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="content">{{ item.content|safe }}</div>
|
||||
<div class="clearfix"></div>
|
||||
{% if config.show_share_buttons %}
|
||||
<div class="share">
|
||||
<a href="https://twitter.com/share" class="twitter-share-button" data-text="{{ item.title }} - {{ config.base_url }}{{ item.get_absolute_url() }}" data-via="{{ config.twitter_username }}" data-size="large">Tweet</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
{% endif %}
|
||||
{% if config.disqus_shortname and config.enable_comments %}
|
||||
<hr class="big" />
|
||||
<div class="comments" id="disqus_thread"></div>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
{% block endbody %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var LiveEditCtrl = function(window) {
|
||||
this.UI = {
|
||||
'liveEditButton': document.querySelector('.blog-entry [data-button="live-edit"]'),
|
||||
'saveButton': document.querySelector('.blog-entry [data-button="save"]'),
|
||||
'entryContent': document.querySelector('.blog-entry .content')
|
||||
}
|
||||
this.endpoints = {
|
||||
'liveEdit': document.querySelector('.blog-entry').getAttribute('data-liveedit-url')
|
||||
}
|
||||
this.editorOptions = {
|
||||
'class': 'liveeditor',
|
||||
'editor': this.UI.entryContent
|
||||
}
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.initialize = function() {
|
||||
var _this = this;
|
||||
// Ensure save button is hidden
|
||||
this.UI.saveButton.classList.add('hidden');
|
||||
// Add handler for live edit button
|
||||
this.UI.liveEditButton.addEventListener('click', function(event){
|
||||
_this.loadEditor();
|
||||
_this.hideButton('liveEdit');
|
||||
_this.showButton('save');
|
||||
});
|
||||
this.UI.saveButton.addEventListener('click', function(event){
|
||||
_this.save(function(success){
|
||||
_this.unloadEditor();
|
||||
_this.hideButton('save');
|
||||
_this.showButton('liveEdit');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.hideButton = function(btn) {
|
||||
this.UI[btn + 'Button'].classList.add('hidden');
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.showButton = function(btn) {
|
||||
this.UI[btn + 'Button'].classList.remove('hidden');
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.loadEditor = function() {
|
||||
this._lastSavedContent = this.UI.entryContent.innerHTML
|
||||
this.editor = new Pen(this.editorOptions);
|
||||
this.startAutoSave();
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.unloadEditor = function() {
|
||||
this.editor.destroy();
|
||||
this.stopAutoSave();
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.save = function(callback) {
|
||||
var _this = this,
|
||||
r = new XMLHttpRequest();
|
||||
|
||||
r.open("POST", this.endpoints.liveEdit, true);
|
||||
r.onreadystatechange = function () {
|
||||
if (r.readyState != 4 || r.status != 200) {
|
||||
_this._lastSavedContent = _this.UI.entryContent.innerHTML;
|
||||
if (callback) return callback(true, r.status);
|
||||
}
|
||||
};
|
||||
data = {
|
||||
'content': this.UI.entryContent.innerHTML,
|
||||
'markdown': this.editor.toMd()
|
||||
}
|
||||
r.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.startAutoSave = function() {
|
||||
var _this = this;
|
||||
this._autoSaveInterval = setInterval(function(){
|
||||
if (_this._lastSavedContent != _this.UI.entryContent.innerHTML) {
|
||||
console.log('Auto saved');
|
||||
_this.save();
|
||||
} else {
|
||||
console.log('Not autosaving because content is the same')
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.stopAutoSave = function() {
|
||||
clearInterval(this._autoSaveInterval);
|
||||
}
|
||||
|
||||
window.LiveEditCtrl = new LiveEditCtrl(window);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,41 +0,0 @@
|
|||
{% extends "_layout.jinja" %}
|
||||
|
||||
{% block page_title %}Blog{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" href="{{ static('bower/highlight/src/styles/railscasts.css') }}" />
|
||||
<link rel="stylesheet" href="{{ static('bower/pen/src/pen.css') }}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
<hr />
|
||||
|
||||
{% block submenu %}
|
||||
<form action="{{ url('blog:search') }}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="text" class="search-field" name="query" placeholder="Search in blog..."
|
||||
{% if search_query %}
|
||||
value=""
|
||||
{% endif %}
|
||||
/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
<a href="{{ url('blog:rss') }}" class="button rss">
|
||||
<i class="fa fa-rss"></i> RSS
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block endbody %}
|
||||
{{ super() }}
|
||||
{% if config.disqus_shortname and config.enable_comments %}
|
||||
{% from '_macros.jinja' import disqus with context %}
|
||||
{{ disqus() }}
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<script type="text/javascript" src="{{ static('bower/pen/src/pen.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ static('bower/pen/src/markdown.js') }}"></script>
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/languages/lisp.min.js"></script>
|
||||
<script type="text/javascript">hljs.initHighlightingOnLoad();</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"directory": "./static/bower"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "fmartingr.com",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://gitlab.com/fmartingr/fmartingr.com",
|
||||
"authors": [
|
||||
"Felipe Martin <fmartingr@me.com>"
|
||||
],
|
||||
"license": "GPLv2",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"font-awesome": "~4.5.0",
|
||||
"google-code-prettify": "~1.0.3",
|
||||
"jquery": "~2.1.3",
|
||||
"lightbox2": "~2.7.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"css": [
|
||||
""
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
|
@ -0,0 +1,26 @@
|
|||
(function() {
|
||||
var toggleMenu,
|
||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
toggleMenu = function() {
|
||||
var button, sidebar;
|
||||
|
||||
sidebar = document.querySelector('.sidebar');
|
||||
button = document.querySelector('button.menu');
|
||||
if (__indexOf.call(button.classList, 'menu-shown') >= 0) {
|
||||
sidebar.classList.remove('shown');
|
||||
return button.classList.remove('menu-shown');
|
||||
} else {
|
||||
sidebar.classList.add('shown');
|
||||
return button.classList.add('menu-shown');
|
||||
}
|
||||
};
|
||||
|
||||
window.onload = function() {
|
||||
var button;
|
||||
|
||||
button = document.querySelector('button.menu');
|
||||
return button.onclick = toggleMenu;
|
||||
};
|
||||
|
||||
}).call(this);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue