Compare commits

...

25 Commits
v3 ... master

Author SHA1 Message Date
Felipe Martín 1982223267 Fixed sha1 checksum not seeking the file init 2016-04-27 20:10:38 +02:00
Felipe Martín 257c6a539c Changed page to GET and added main attachment view 2016-04-11 20:22:37 +02:00
Felipe Martín bb8ebf13b6 Fixed RSSView 2016-04-09 11:48:28 +02:00
Felipe Martín e1d82b5634 Added attachment URL shortcuts 2016-04-07 22:20:15 +02:00
Felipe Martín 66e883c8e5 Added admin attachment inline admin (basic) 2016-03-21 23:00:13 +01:00
Felipe Martín 6403802932 Added blog.Entry.attachments 2016-03-21 22:50:59 +01:00
Felipe Martín 8b1c2cb3c6 Better upload helper and admin integration 2016-03-21 22:49:39 +01:00
Felipe Martín acb4e22739 Added Attachment model 2016-03-21 21:54:17 +01:00
Felipe Martín 11e5412de6 markdown field takes more importance over content 2016-03-13 13:51:36 +01:00
Felipe Martín 51c9cf3c62 Added markdown field 2016-03-13 10:53:18 +01:00
Felipe Martín d8a8d4467b Admin CKEditor advanced toolbar and no filtering 2016-03-13 00:13:52 +01:00
Felipe Martín fa7e00f1bb Fixed some asset inclusion 2016-03-08 21:39:25 +01:00
Felipe Martín f48900d586 Bigger text for the blog 2016-03-06 14:30:13 +01:00
Felipe Martín b7b01df9f0 Added telegram button to homepage 2016-03-06 14:27:48 +01:00
Felipe Martín f10d3ae94f Updated deploy script for django 1.9 2016-03-06 13:45:53 +01:00
Felipe Martín 7d84ef1a7e Fixed pure buttons css path from another repo 2016-03-06 13:39:47 +01:00
Felipe Martín 086f3529f4 Live editor finished
- Edit and saves entries in webpage
- Autosaves every 10 seconds
- Don't save if no changes
- django-reversion working
2016-03-06 13:29:21 +01:00
Felipe Martín 9d6302ead3 Added management buttons for an entry
- Editor is created/destroyed
- Admin link working
- Fixed some styling
2016-03-06 12:19:45 +01:00
Felipe Martín cea42616df Added entry edit buttons 2016-03-06 11:47:54 +01:00
Felipe Martín 51cdbfce2c Added pen editor for liveedit entries 2016-03-06 11:28:18 +01:00
Felipe Martín 732a6ffc95 Ignore more files 2016-03-06 11:11:59 +01:00
Felipe Martín 8dd7e3e819 Revert "Added markdown field into blog.Entry"
This reverts commit 84a94fc475.
2016-03-06 11:11:03 +01:00
Felipe Martín 9dc393925e Added MEDIA_ROOT for local assets 2016-03-06 11:10:27 +01:00
Felipe Martín 84a94fc475 Added markdown field into blog.Entry 2016-03-06 09:54:23 +01:00
Felipe Martín f9524c88e0 Updated dependencies. Welcome django 1.9 2016-03-06 01:18:40 +01:00
34 changed files with 716 additions and 342 deletions

4
.gitignore vendored
View File

@ -1,10 +1,12 @@
*.pyc
.virtualenv
.DS_Store
*.sqlite3
node_modules
**/bower
bower_components
**/CACHE/*
*.sublime-workspace
.sass-cache
db.sqlite3
/projects
/fmartingrcom/media

View File

@ -17,6 +17,9 @@
"font-awesome": "~4.2.0",
"google-code-prettify": "~1.0.3",
"jquery": "~2.1.3",
"lightbox2": "~2.7.1"
"lightbox2": "~2.7.1",
"pen": "^0.2.2",
"pure": "^0.6.0",
"highlight": "^9.2.0"
}
}

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
from fabric.colors import yellow, red, white, green, blue # blue is used
#
@ -230,11 +230,7 @@ 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 syncdb {}'.format(managepy_affix)
subheader(cmd)
run(cmd)
cmd = 'python code/manage.py migrate {} --no-initial-data'\
cmd = 'python code/manage.py migrate {} --no-input'\
.format(managepy_affix)
subheader(cmd)
run(cmd)

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from django.views.generic import View as DjangoView
@ -6,5 +7,6 @@ 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

@ -1,35 +1,80 @@
from django.contrib import admin
from .models import Entry, Tag
from .models import Entry, Tag, Attachment
from ckeditor.widgets import CKEditorWidget
from django.utils.translation import ugettext as _
import reversion
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(reversion.VersionAdmin):
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', )
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 = [
(None, {
'classes': ('suit-tab suit-tab-general',),
'fields': ('title', 'slug', 'draft', 'date', 'tags', )
('General', {
'classes': ('suit-tab suit-tab-general collapse',),
'fields': (('title', 'slug', 'draft'), ('date', 'tags'), )
}),
(None, {
'classes': ('suit-tab suit-tab-content full-width',),
('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):
@ -53,7 +98,7 @@ admin.site.register(Entry, EntryAdmin)
#
# TAG
#
class TagAdmin(reversion.VersionAdmin):
class TagAdmin(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', ckeditor.fields.RichTextField()),
('content', models.TextField()),
('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,25 @@
# -*- 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

@ -0,0 +1,31 @@
# -*- 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

@ -0,0 +1,20 @@
# -*- 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,10 +1,25 @@
# -*- 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 datetime import datetime
from django.core.files import File
from django.utils.timezone import utc
from ckeditor.fields import RichTextField
from django.core.urlresolvers import reverse
from django.utils.translation import activate
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()
#
@ -13,7 +28,8 @@ from django.utils.translation import activate
class Entry(models.Model):
title = models.CharField(max_length=128)
date = models.DateTimeField()
content = RichTextField()
content = models.TextField()
markdown = models.TextField(blank=True)
slug = models.SlugField(max_length=128)
draft = models.BooleanField(default=True)
author = models.ForeignKey(
@ -21,7 +37,9 @@ class Entry(models.Model):
editable=False,
related_name='author'
)
tags = models.ManyToManyField('Tag', null=True, blank=True)
tags = models.ManyToManyField('Tag', blank=True)
attachments = models.ManyToManyField('Attachment', blank=True,
related_name='entries')
def __unicode__(self):
return self.title
@ -48,6 +66,13 @@ class Entry(models.Model):
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']
@ -67,3 +92,106 @@ class Tag(models.Model):
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,15 +1,17 @@
from django.conf.urls import patterns, url
from django.conf.urls import url
from .views import ListView, EntryView, SearchView, RSSView
from .views import (
ListView, AttachmentView,
EntryView, EntryAttachmentView, EntryLiveEditView,
SearchView, RSSView)
urlpatterns = patterns(
None,
# Post list with page
urlpatterns = [
# Global attachment URL
url(
r'^page/(?P<page_number>\d+)/$',
ListView.as_view(),
name='list'
r'^attachment/(?P<attachment_id>\d+)$',
AttachmentView.as_view(),
name='attachment'
),
# Post list
url(
@ -23,6 +25,18 @@ urlpatterns = patterns(
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$',
@ -35,4 +49,4 @@ urlpatterns = patterns(
SearchView.as_view(),
name='search',
)
)
]

View File

@ -1,8 +1,6 @@
# -*- 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
@ -20,8 +18,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

@ -1,14 +1,14 @@
from datetime import datetime
# -*- coding: utf-8 -*-
import json
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
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
from .models import Entry, Attachment
from fmartingrcom.apps._core.views import View
from fmartingrcom.apps.config.models import SiteConfiguration
@ -16,13 +16,21 @@ 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=1):
if 'page' in request.GET:
page_number = int(request.GET['page'])
def get(self, request):
page_number = int(request.GET.get('page', 1))
paginator, page = blog_utils.get_paginator(request, page_number)
@ -30,15 +38,55 @@ class ListView(View):
self.data['page_number'] = page_number
self.data['paginator'] = paginator
context = RequestContext(request, self.data)
return render_to_response(self.template, context_instance=context)
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,
@ -51,14 +99,17 @@ class EntryView(View):
except Entry.DoesNotExist:
raise Http404
paginator, page = blog_utils.get_paginator(request, item=item)
data = json.loads(request.body)
if 'markdown' in data:
item.markdown = data['markdown']
item.save()
return HttpResponse(status=200)
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)
if 'content' in data:
item.content = data['content']
item.save()
return HttpResponse(status=200)
return HttpResponse(status=204)
class SearchView(ListView):
@ -83,8 +134,7 @@ class SearchView(ListView):
self.data['paginator'] = paginator
self.data['search_query'] = search_query
context = RequestContext(request, self.data)
return render_to_response(self.template, context_instance=context)
return render(request, self.template, self.data)
class RSSView(View):
@ -95,9 +145,5 @@ class RSSView(View):
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'
)
return render(request, self.template, self.data,
content_type='text/xml')

View File

@ -1,13 +1,12 @@
from django.conf.urls import patterns, url
# -*- coding: utf-8 -*-
from django.conf.urls import url
from .views import HomepageView
urlpatterns = patterns(
None,
urlpatterns = [
url(
r'^$',
HomepageView.as_view(),
name='homepage'
),
)
]

View File

@ -1,5 +1,5 @@
from django.shortcuts import render_to_response
from django.template import RequestContext
# -*- coding: utf-8 -*-
from django.shortcuts import render
from fmartingrcom.apps._core.views import View
@ -8,5 +8,4 @@ class HomepageView(View):
section = 'homepage'
def get(self, request):
context = RequestContext(request, self.data)
return render_to_response(self.template, context_instance=context)
return render(request, self.template, self.data)

View File

@ -4,7 +4,7 @@
from django.contrib import admin
# 3rd party
import reversion
from reversion.admin import VersionAdmin
# app
from . import models
@ -13,7 +13,7 @@ from . import models
#
# Group
#
class GroupAdmin(reversion.VersionAdmin):
class GroupAdmin(VersionAdmin):
list_display = ('name', 'order', )
list_display_links = ('name', )
list_editable = ('order', )
@ -36,11 +36,17 @@ class ProjectImageInline(admin.TabularInline):
model = models.ProjectImage
class ProjectAdmin(reversion.VersionAdmin):
class ProjectAdmin(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,7 +3,6 @@ from __future__ import unicode_literals
from django.db import models, migrations
import fmartingrcom.apps.projects.models
import ckeditor.fields
class Migration(migrations.Migration):
@ -33,7 +32,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', ckeditor.fields.RichTextField()),
('description', models.TextField()),
('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,16 +1,12 @@
# 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
#
@ -32,7 +28,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 = RichTextField()
description = models.TextField()
visible = models.BooleanField(default=True)
stack = models.CharField(max_length=256, null=True, blank=True,
default=None)

View File

@ -1,18 +1,13 @@
# coding: utf-8
# django
from django.conf.urls import patterns, url
# app
# -*- coding: utf-8 -*-
from django.conf.urls import url
from . import views
urlpatterns = patterns(
None,
urlpatterns = [
# Project list
url(
r'^$',
views.ListView.as_view(),
name='list'
),
)
]

View File

@ -1,14 +1,7 @@
# coding: utf-8
# django
from django.shortcuts import render_to_response
from django.template import RequestContext
# project
# -*- coding: utf-8 -*-
from django.shortcuts import render
from fmartingrcom.apps._core.views import View
from fmartingrcom.apps.config.models import SiteConfiguration
# app
from . import models
config = SiteConfiguration.objects.get()
@ -25,5 +18,4 @@ class ListView(View):
'groups': models.Group.objects.all().order_by('order'),
}
context = RequestContext(request, data)
return render_to_response(self.template, context_instance=context)
return render(request, self.template, data)

View File

@ -15,16 +15,13 @@ 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',
@ -131,12 +128,14 @@ THUMBNAIL_ALIASES = {
# MEDIA FILES
#
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
CKEDITOR_UPLOAD_PATH = 'ckeditor/'
CKEDITOR_CONFIGS = {
'default': {
'toolbar': 'Standard',
'toolbar': 'Advanced',
'allowedContent': True,
'width': '100%',
'stylesSet': (
{
@ -166,44 +165,61 @@ CKEDITOR_CONFIGS = {
#
# TEMPLATES
#
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_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_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

@ -31,9 +31,13 @@ article.blog-entry
.info
.content
line-height: 140%
font-size: 1.2em
line-height: 150%
padding-top: 15px
&.liveeditor
outline: 0
img
box-shadow: $sidebar-bg 0 0 4px
max-width: 100%
@ -54,7 +58,7 @@ article.blog-entry
> :last-child
margin-bottom: 0
code
p code, li code, td code
display: inline-block
white-space: no-wrap
background: #fff
@ -70,6 +74,10 @@ article.blog-entry
padding: 0 .3em
margin: -1px 0
pre code
font-size: .8em
line-height: 1.2em
hr
width: 50%

View File

@ -77,6 +77,14 @@ section.content
&:hover
background-color: darken($email-color, 12%)
&.telegram
background-color: $telegram-color
color: $telegram-text-color
box-shadow: 0 0 1px $telegram-color
&:hover
background-color: darken($telegram-color, 12%)
&.twitter
background-color: $twitter-color
color: $twitter-text-color
@ -100,4 +108,3 @@ section.content
&:hover
background-color: darken($linkedin-color, 12%)

View File

@ -1,84 +0,0 @@
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

@ -32,6 +32,9 @@ $twitter-text-color: #fff
$email-color: #db4437
$email-text-color: #e7e6dd
$telegram-color: #0088cc
$telegram-text-color: white
$github-color: #999
$github-text-color: #000

View File

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

View File

@ -4,9 +4,10 @@
<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 %}
@ -66,7 +67,7 @@
</section>
{% compress js %}
<script type="text/javascript" src="{{ STATIC_URL }}js/mobile.js"></script>
<script type="text/javascript" src="{{ static('js/mobile.js') }}"></script>
{% block javascript %}{% endblock %}
{% endcompress %}
{% if config.google_analytics %}

View File

@ -1,53 +1,158 @@
{% extends "blog/layout.jinja" %}
{% block page_title %}
{{ super() }} | {{ item.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 %}
{% 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 %}
<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>
<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">
<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 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>
<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>
{% 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

@ -3,36 +3,39 @@
{% 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>
<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 />
<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>
{% 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 %}
{{ 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

@ -65,17 +65,17 @@
<div class="pagination">
{% if page.has_next() %}
<a href="{{ url('blog:list', page.next_page_number()) }}" class="button prev-page pull-right gap half">
<a href="{{ url('blog:list') }}?page={{ page.next_page_number() }}" class="button prev-page pull-right gap half">
Older <i class="fa fa-angle-double-right"></i>
</a>
<a href="#">
<a href="#">o
{% 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.previous_page_number()) }}" class="button prev-page pull-left gap half">
<a href="{{ url('blog:list') }}?page={{ 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', p) }}">{{ p }}</a>
<a href="{{ url('blog:list') }}">?page={{ p }}</a>
</li>
{% endfor %}
</ul>

View File

@ -3,23 +3,24 @@
{% 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 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. Whatever that is.</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="https://telegram.me/fmartingr" class="button telegram"><span class="fa fa-paper-plane"></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 patterns, include, url
from django.conf.urls import include, url
from django.contrib import admin
from .apps.homepage.sitemap import HomeSitemap
@ -13,15 +13,14 @@ sitemaps = {
admin.autodiscover()
urlpatterns = patterns(
'',
(r'^ckeditor/', include('ckeditor.urls')),
urlpatterns = [
#(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

18
fmartingrcom/utils.py Normal file
View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
import hashlib
import uuid
from django.core.files.uploadedfile import InMemoryUploadedFile
def sha1_checksum(handler):
if isinstance(handler, InMemoryUploadedFile):
temp_name = '/tmp/_{}'.format(uuid.uuid4())
f = open(temp_name, 'wb+')
f.write(handler.read())
f.seek(0)
else:
f = open(handler.name, 'rb')
checksum = hashlib.sha1(f.read())
return checksum.hexdigest()

View File

@ -1,17 +1,19 @@
Django==1.7.7
Django==1.9.4
Jinja2==2.7.3
django-jinja==1.3.1
Jinja2==2.8
django-jinja==2.1.2
dj-database-url==0.3.0
dj-database-url==0.4.0
django-suit==0.2.12
django-reversion==1.8.5
django-solo==1.1.0
django-ckeditor-updated==4.4.4
django-suit==0.2.18
django-reversion==1.10.1
django-solo==1.1.2
django-ckeditor==5.0.3
pytz==2014.10
pytz==2015.7
django-compressor==1.4
django-compressor==2.0
easy-thumbnails==2.2
easy-thumbnails==2.3
mistune==0.7.2