Журнальная вёрстка
   Делаем “красиво” с Django

           Алексей Дубков
            alex@evid.ru


       MoscowDjango, 01/03/2012
CMS против

• Раньше верстали руками
• Сейчас копируют и вставляют
• Скучный “Текст”
• HTML-редактор не спасает
• Скудное оформление
Что хочется

• Перемешивать текст и графику
• Делать выноски, плашки
• Делать колонки
• Расставлять акценты
• Галерею, вот прямо сюда
И так...
В чём идея?

• Любой элемент — блок
• Блоки: текст + графика
• Сверху-вниз
• Слева-направо
• Блоки независимы друг от друга
Итак, блоки

• Они могут быть разных типов
• Как-то отсортированы
• Привязаны к Статье, Новости и т.д.
# blocks/models.py

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class Block(models.Model):
    BLOCK_TYPES = (
        (11, u'T-01: Обычный текст'),
        (21, u'P-01: Картинки 250x в 3 колонки'),)
    CAN_COLLAPSE = (21,)

    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey(
           'content_type', 'object_id')
    block_type = models.IntegerField(choices=BLOCK_TYPES)
    sort = models.IntegerField(default=20)
    image = models.ImageField(blank=True)
    title = models.CharField(max_length=255, blank=True)
    text = models.TextField(blank=True)
# blocks/admin.py

from django.contrib import admin
from django.contrib.contenttypes.generic import
GenericStackedInline
from blocks.models import Block

class BlockInline(GenericStackedInline):
    model = Block
    extra = 0
    fieldsets = (
        (None, {
             'fields': (('block_type', 'sort',),
            'image', 'title', 'text',)
        }),
    )
Вывести блоки

• Выбираем блоки для статьи
• Склеиваем (CAN_COLLAPSE)
• Отдельный рендер для блока
• Склеиваем конечный HTML
# blocks/utils.py

from django.template import RequestContext, loader
from django.contrib.contenttypes.models import ContentType
from blocks.models import Block

def get_blocks_html(request, obj):
    content_type = ContentType.objects.get(
       model=obj.__class__.__name__)
    blocks = Block.objects.filter(
       content_type=content_type,
       object_id=obj.id).order_by('sort')
    blocks_count = blocks.count()
# blocks/utils.py
    ...
    bl = []     # blocks tuple (type, data)
    if blocks_count:
        it = 0
        i = 0
        while i < blocks_count:
            type = blocks[i].block_type
            if type in Block.CAN_COLLAPSE:
                result = []
                it = i
                for j in blocks[i:]:
                     if j.block_type == type:
                         result.append(j)
                         it += 1
                     else:
                         break
                bl.append([type, {'blocks':result}])
                i = it - 1
            else:
                bl.append([type, blocks[i].__dict__])
            i += 1
# blocks/utils.py
    ...
    html = ''   # result html
    if blocks_count:
        for (type, item) in bl:
            t_name = "blocks/block-%s.html" % type
            t = loader.get_template(t_name)
            render = t.render(
               RequestContext(request, item))
            html += render
    return html
Шаблоны

• В простых шаблонах сразу выводятся
  переменные {{ title }}, {{ text }}...
• В склееных шаблонах нужен цикл
• {{ for item in blocks }}
Пример

• Делаем статьи
• Подключаем админку
• Блоки подключаем через inlines
• Делаем вывод
# articles/models.py

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=64)
    pub_date = models.DateField()
    blocks_html = models.TextField(blank=True)

    class Meta:
        ordering = ['-pub_date']
# articles/admin.py

from django.contrib import admin
from articles.models import Article
from blocks.admin import BlockInline

class ArticleAdmin(admin.ModelAdmin):
    save_on_top = True
    inlines = [BlockInline, ]
    list_display = ( 'title', 'pub_date',)

admin.site.register(Article, ArticleAdmin)
# articles/views.py

def articles_one(request, id):
    obj = get_object_or_404(Article, id=id)
    blocks_html = get_blocks_html(request, obj)

    return render_to_response(
      'articles/articles_one.html',
      {'obj': obj,'blocks_html': blocks_html,},
      context_instance=RequestContext(request))
Ньюансы

• Как хранить картинки
• Утомительная сортировка
• Производительность
# blocks/models.py

def image_upload(instance, filename):
    return "block/%s/%s" %
    (instance.content_object.block_image_path(),
    filename.lower())



# artiles/models.py

class Article(models.Model):
    ...
    def block_image_path(self):
        return "article/%d/%02d/%d" % (
               self.pub_date.year,
               self.pub_date.month,
               self.id)
$(document).ready(function(){
$('#block-block-content_type-object_id-group .add-row
a').click(function(){

var totalBlocks=$('#id_block-block-content_type-object_id-
TOTAL_FORMS').val();

var sortFieldId="#id_block-block-content_type-
object_id-"+parseInt(totalBlocks-2)+"-sort";

var newSortFieldId="#id_block-block-content_type-
object_id-"+parseInt(totalBlocks-1)+"-sort";

var lastSortVal=$(sortFieldId).val();
var newSortVal=parseInt(lastSortVal)+10;
$(newSortFieldId).val(newSortVal);

})
Скорость

• Производительность низкая
• Можно сохранять HTML
• Переопределив save_model в админке
  сохранять дважды
Минусы

• Сложно в обучении
• Можно “накакафонить”
• Подгонять контент под формат
Спасибо

Журнальная вёрстка в Django

  • 1.
    Журнальная вёрстка Делаем “красиво” с Django Алексей Дубков alex@evid.ru MoscowDjango, 01/03/2012
  • 2.
    CMS против • Раньшеверстали руками • Сейчас копируют и вставляют • Скучный “Текст” • HTML-редактор не спасает • Скудное оформление
  • 5.
    Что хочется • Перемешиватьтекст и графику • Делать выноски, плашки • Делать колонки • Расставлять акценты • Галерею, вот прямо сюда
  • 7.
  • 11.
    В чём идея? •Любой элемент — блок • Блоки: текст + графика • Сверху-вниз • Слева-направо • Блоки независимы друг от друга
  • 14.
    Итак, блоки • Онимогут быть разных типов • Как-то отсортированы • Привязаны к Статье, Новости и т.д.
  • 15.
    # blocks/models.py from django.dbimport models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic class Block(models.Model): BLOCK_TYPES = ( (11, u'T-01: Обычный текст'), (21, u'P-01: Картинки 250x в 3 колонки'),) CAN_COLLAPSE = (21,) content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey( 'content_type', 'object_id') block_type = models.IntegerField(choices=BLOCK_TYPES) sort = models.IntegerField(default=20) image = models.ImageField(blank=True) title = models.CharField(max_length=255, blank=True) text = models.TextField(blank=True)
  • 16.
    # blocks/admin.py from django.contribimport admin from django.contrib.contenttypes.generic import GenericStackedInline from blocks.models import Block class BlockInline(GenericStackedInline): model = Block extra = 0 fieldsets = ( (None, { 'fields': (('block_type', 'sort',), 'image', 'title', 'text',) }), )
  • 17.
    Вывести блоки • Выбираемблоки для статьи • Склеиваем (CAN_COLLAPSE) • Отдельный рендер для блока • Склеиваем конечный HTML
  • 18.
    # blocks/utils.py from django.templateimport RequestContext, loader from django.contrib.contenttypes.models import ContentType from blocks.models import Block def get_blocks_html(request, obj): content_type = ContentType.objects.get( model=obj.__class__.__name__) blocks = Block.objects.filter( content_type=content_type, object_id=obj.id).order_by('sort') blocks_count = blocks.count()
  • 19.
    # blocks/utils.py ... bl = [] # blocks tuple (type, data) if blocks_count: it = 0 i = 0 while i < blocks_count: type = blocks[i].block_type if type in Block.CAN_COLLAPSE: result = [] it = i for j in blocks[i:]: if j.block_type == type: result.append(j) it += 1 else: break bl.append([type, {'blocks':result}]) i = it - 1 else: bl.append([type, blocks[i].__dict__]) i += 1
  • 20.
    # blocks/utils.py ... html = '' # result html if blocks_count: for (type, item) in bl: t_name = "blocks/block-%s.html" % type t = loader.get_template(t_name) render = t.render( RequestContext(request, item)) html += render return html
  • 21.
    Шаблоны • В простыхшаблонах сразу выводятся переменные {{ title }}, {{ text }}... • В склееных шаблонах нужен цикл • {{ for item in blocks }}
  • 22.
    Пример • Делаем статьи •Подключаем админку • Блоки подключаем через inlines • Делаем вывод
  • 23.
    # articles/models.py from django.dbimport models class Article(models.Model): title = models.CharField(max_length=64) pub_date = models.DateField() blocks_html = models.TextField(blank=True) class Meta: ordering = ['-pub_date']
  • 24.
    # articles/admin.py from django.contribimport admin from articles.models import Article from blocks.admin import BlockInline class ArticleAdmin(admin.ModelAdmin): save_on_top = True inlines = [BlockInline, ] list_display = ( 'title', 'pub_date',) admin.site.register(Article, ArticleAdmin)
  • 26.
    # articles/views.py def articles_one(request,id): obj = get_object_or_404(Article, id=id) blocks_html = get_blocks_html(request, obj) return render_to_response( 'articles/articles_one.html', {'obj': obj,'blocks_html': blocks_html,}, context_instance=RequestContext(request))
  • 28.
    Ньюансы • Как хранитькартинки • Утомительная сортировка • Производительность
  • 29.
    # blocks/models.py def image_upload(instance,filename): return "block/%s/%s" % (instance.content_object.block_image_path(), filename.lower()) # artiles/models.py class Article(models.Model): ... def block_image_path(self): return "article/%d/%02d/%d" % ( self.pub_date.year, self.pub_date.month, self.id)
  • 30.
    $(document).ready(function(){ $('#block-block-content_type-object_id-group .add-row a').click(function(){ var totalBlocks=$('#id_block-block-content_type-object_id- TOTAL_FORMS').val(); varsortFieldId="#id_block-block-content_type- object_id-"+parseInt(totalBlocks-2)+"-sort"; var newSortFieldId="#id_block-block-content_type- object_id-"+parseInt(totalBlocks-1)+"-sort"; var lastSortVal=$(sortFieldId).val(); var newSortVal=parseInt(lastSortVal)+10; $(newSortFieldId).val(newSortVal); })
  • 31.
    Скорость • Производительность низкая •Можно сохранять HTML • Переопределив save_model в админке сохранять дважды
  • 32.
    Минусы • Сложно вобучении • Можно “накакафонить” • Подгонять контент под формат
  • 35.