Compare commits

..

14 Commits
master ... v3

Author SHA1 Message Date
Felipe Martín 26ec4814e4 Added mediaqueries sass 2016-03-05 20:55:22 +01:00
Felipe Martín a83eb4a58e Better blog stuff 2016-03-05 20:33:27 +01:00
Felipe Martín d73e57bff6 Added blog app with model 2016-03-05 18:36:58 +01:00
Felipe Martín 4f5505ceba Themes 2015-12-07 09:33:29 +01:00
Felipe Martín 80634771ac Blog WIP 2015-12-07 00:24:31 +01:00
Felipe Martín 4b685bb51c Added peewee as ORM 2015-12-07 00:24:11 +01:00
Felipe Martín 311f79a155 Fix blog blueprint name 2015-12-06 21:13:05 +01:00
Felipe Martín e9f2304008 New layout & home 2015-12-06 21:12:54 +01:00
Felipe Martín 5ca6115ed5 Gulp + Sass + Livereload 2015-12-06 20:16:10 +01:00
Felipe Martín bba9109c82 main.py -> runserver.py 2015-12-06 12:50:38 +01:00
Felipe Martín 1c3eebb5ca Views -> Blueprints 2015-12-05 00:17:03 +01:00
Felipe Martín 16c6cbceb6 README 2015-12-02 23:59:17 +01:00
Felipe Martín 79dd3339d1 Added templates 2015-12-02 23:37:20 +01:00
Felipe Martín 97329994f6 v3 structure 2015-12-02 22:57:44 +01:00
146 changed files with 2772 additions and 999 deletions

View File

@ -1,3 +1,3 @@
{
"directory": "fmartingrcom/themes/v1/static/bower"
}
"directory": "fmartingrcom/static/bower_components"
}

26
.gitignore vendored
View File

@ -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

View File

@ -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
```

6
_old/README.md Normal file
View File

@ -0,0 +1,6 @@
fmartingr.com
===
My personal site codebase.
http://fmartingr.com

22
_old/bower.json Normal file
View File

@ -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"
}
}

View File

@ -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)

View File

@ -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 &raquo;</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)

View File

@ -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)),

View File

@ -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']

View File

@ -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',
)
)

View File

@ -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()

View File

@ -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'
)

View File

@ -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'
),
]
)

View File

@ -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)

View File

@ -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)

View File

@ -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)),

View File

@ -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)

View File

@ -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'
),
]
)

View File

@ -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)

View File

View File

@ -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
#

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -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%

View File

@ -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%)

View File

@ -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

View File

@ -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')

View File

@ -5,5 +5,6 @@
@import "homepage"
@import "blog"
@import "projects"
@import "syntax"
@import "responsive"

View File

@ -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 %}

View File

@ -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 %}
&nbsp;
<i class="fa fa-comment"></i> <a href="{{ item.get_absolute_url() }}#disqus_thread">0 Comments</a>
{% endif %}
{% if item.tags.count() > 0 %}
&nbsp;
<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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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

11
_old/manage.py Executable file
View File

@ -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)

19
_old/package.json Normal file
View File

@ -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"
}
}

17
_old/requirements.txt Normal file
View File

@ -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

View File

@ -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
fabfile.py vendored
View File

@ -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)

View File

@ -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
fmartingrcom/app.py Normal file
View File

View File

@ -0,0 +1 @@
from . import home, blog, portfolio

55
fmartingrcom/apps/blog.py Normal file
View File

@ -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)

View File

@ -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 &raquo;</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)

View File

@ -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'),
),
]

View File

@ -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',
},
),
]

View File

@ -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'),
),
]

View File

@ -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)

View File

@ -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',
)
]

View File

@ -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')

11
fmartingrcom/apps/home.py Normal file
View File

@ -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')

View File

@ -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)

View File

@ -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)

37
fmartingrcom/conf.py Normal file
View File

@ -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

3
fmartingrcom/database.py Normal file
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from sqlalchemy.ext.declarative import declarative_base
Model = declarative_base()

View File

@ -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 %}
&nbsp;
<i class="fa fa-comment"></i> <a href="{{ item.get_absolute_url() }}#disqus_thread">0 Comments</a>
{% endif %}
{% if item.tags.count() > 0 %}
&nbsp;
<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 %}

View File

@ -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 %}

View File

@ -0,0 +1,3 @@
{
"directory": "./static/bower"
}

View File

@ -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"
}
}

View File

@ -0,0 +1,5 @@
{
"css": [
""
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -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