Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
Felipe Martín | 1982223267 | |
Felipe Martín | 257c6a539c | |
Felipe Martín | bb8ebf13b6 | |
Felipe Martín | e1d82b5634 | |
Felipe Martín | 66e883c8e5 | |
Felipe Martín | 6403802932 | |
Felipe Martín | 8b1c2cb3c6 | |
Felipe Martín | acb4e22739 | |
Felipe Martín | 11e5412de6 | |
Felipe Martín | 51c9cf3c62 | |
Felipe Martín | d8a8d4467b | |
Felipe Martín | fa7e00f1bb | |
Felipe Martín | f48900d586 | |
Felipe Martín | b7b01df9f0 | |
Felipe Martín | f10d3ae94f | |
Felipe Martín | 7d84ef1a7e | |
Felipe Martín | 086f3529f4 | |
Felipe Martín | 9d6302ead3 | |
Felipe Martín | cea42616df | |
Felipe Martín | 51cdbfce2c | |
Felipe Martín | 732a6ffc95 | |
Felipe Martín | 8dd7e3e819 | |
Felipe Martín | 9dc393925e | |
Felipe Martín | 84a94fc475 | |
Felipe Martín | f9524c88e0 |
|
@ -1,10 +1,12 @@
|
|||
*.pyc
|
||||
.virtualenv
|
||||
.DS_Store
|
||||
*.sqlite3
|
||||
node_modules
|
||||
**/bower
|
||||
bower_components
|
||||
**/CACHE/*
|
||||
*.sublime-workspace
|
||||
.sass-cache
|
||||
db.sqlite3
|
||||
/projects
|
||||
/fmartingrcom/media
|
|
@ -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,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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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%
|
||||
|
||||
|
|
|
@ -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%)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
@import "homepage"
|
||||
@import "blog"
|
||||
@import "projects"
|
||||
@import "syntax"
|
||||
|
||||
@import "responsive"
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
<i class="fa fa-comment"></i> <a href="{{ item.get_absolute_url() }}#disqus_thread">0 Comments</a>
|
||||
{% endif %}
|
||||
{% if item.tags.count() > 0 %}
|
||||
|
||||
<i class="fa fa-tag"></i> {{ item.tags.all()|join(', ') }}
|
||||
{% endif %}
|
||||
<div 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 %}
|
||||
|
||||
<i class="fa fa-comment"></i> <a href="{{ item.get_absolute_url() }}#disqus_thread">0 Comments</a>
|
||||
{% endif %}
|
||||
{% if item.tags.count() > 0 %}
|
||||
|
||||
<i class="fa fa-tag"></i> {{ item.tags.all()|join(', ') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="content">{{ item.content|safe }}</div>
|
||||
<div class="clearfix"></div>
|
||||
{% if config.show_share_buttons %}
|
||||
<div class="share">
|
||||
<a href="https://twitter.com/share" class="twitter-share-button" data-text="{{ item.title }} - {{ config.base_url }}{{ item.get_absolute_url() }}" data-via="{{ config.twitter_username }}" data-size="large">Tweet</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
{% endif %}
|
||||
{% if config.disqus_shortname and config.enable_comments %}
|
||||
<hr class="big" />
|
||||
<div class="comments" id="disqus_thread"></div>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
{% block endbody %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var LiveEditCtrl = function(window) {
|
||||
this.UI = {
|
||||
'liveEditButton': document.querySelector('.blog-entry [data-button="live-edit"]'),
|
||||
'saveButton': document.querySelector('.blog-entry [data-button="save"]'),
|
||||
'entryContent': document.querySelector('.blog-entry .content')
|
||||
}
|
||||
this.endpoints = {
|
||||
'liveEdit': document.querySelector('.blog-entry').getAttribute('data-liveedit-url')
|
||||
}
|
||||
this.editorOptions = {
|
||||
'class': 'liveeditor',
|
||||
'editor': this.UI.entryContent
|
||||
}
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.initialize = function() {
|
||||
var _this = this;
|
||||
// Ensure save button is hidden
|
||||
this.UI.saveButton.classList.add('hidden');
|
||||
// Add handler for live edit button
|
||||
this.UI.liveEditButton.addEventListener('click', function(event){
|
||||
_this.loadEditor();
|
||||
_this.hideButton('liveEdit');
|
||||
_this.showButton('save');
|
||||
});
|
||||
this.UI.saveButton.addEventListener('click', function(event){
|
||||
_this.save(function(success){
|
||||
_this.unloadEditor();
|
||||
_this.hideButton('save');
|
||||
_this.showButton('liveEdit');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.hideButton = function(btn) {
|
||||
this.UI[btn + 'Button'].classList.add('hidden');
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.showButton = function(btn) {
|
||||
this.UI[btn + 'Button'].classList.remove('hidden');
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.loadEditor = function() {
|
||||
this._lastSavedContent = this.UI.entryContent.innerHTML
|
||||
this.editor = new Pen(this.editorOptions);
|
||||
this.startAutoSave();
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.unloadEditor = function() {
|
||||
this.editor.destroy();
|
||||
this.stopAutoSave();
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.save = function(callback) {
|
||||
var _this = this,
|
||||
r = new XMLHttpRequest();
|
||||
|
||||
r.open("POST", this.endpoints.liveEdit, true);
|
||||
r.onreadystatechange = function () {
|
||||
if (r.readyState != 4 || r.status != 200) {
|
||||
_this._lastSavedContent = _this.UI.entryContent.innerHTML;
|
||||
if (callback) return callback(true, r.status);
|
||||
}
|
||||
};
|
||||
data = {
|
||||
'content': this.UI.entryContent.innerHTML,
|
||||
'markdown': this.editor.toMd()
|
||||
}
|
||||
r.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.startAutoSave = function() {
|
||||
var _this = this;
|
||||
this._autoSaveInterval = setInterval(function(){
|
||||
if (_this._lastSavedContent != _this.UI.entryContent.innerHTML) {
|
||||
console.log('Auto saved');
|
||||
_this.save();
|
||||
} else {
|
||||
console.log('Not autosaving because content is the same')
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
LiveEditCtrl.prototype.stopAutoSave = function() {
|
||||
clearInterval(this._autoSaveInterval);
|
||||
}
|
||||
|
||||
window.LiveEditCtrl = new LiveEditCtrl(window);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue