БетаЛаборатория - внеочередной IT-блог

Записки обычного программиста

 

Программирование

FloatField с запятой в Django Forms - Паллиатив

Давно собирался сделать возможность вводить Float разделенные не точкой, а запятой, да никак руки не доходили. Но, сегодня все-таки пришлось сделать, поскольку для заказчика это оказалось критично. В принципе, данная задача решена в отдельном бранче Django, однако я предпочел сделать более простое, но менее полное и гибкое решение - создал специальный FormField. Все что он делает - это заменяет в полученных от пользователя данных запятую на точку, после чего передает управления оригинальному FloatField.

from django import forms

class FloatFieldWithComma(forms.FloatField):
	def clean(self, value):
		# Проверяем задано ли вообще значение
		if value:
			# Если да - заменяем запятую на точку
			value = value.replace(",",  ".")
		return super(FloatFieldWithComma,  self).clean(value)

Соответственно, если вы используете ModelForm, вам надо будет переопределить поля с плавающей точкой

# Модель
class MyModel(models.Model):
	name = models.CharField(max_length=150)
	price = models.FloatField()

# Форма для модели. В таком виде форма будет требовать
# чтобы разделитель обязательно был точкой.
class MyModelForm(forms.ModelForm):
	class Meta:
		model = MyModel
# А с переопределенным полем price валидными будут и
# точка и запятая
class MyModelForm(forms.ModelForm):
	price = FloatFieldWithComma()
	class Meta:
		model = MyModel

Недостатки этого метода в том, что он, во-первых, не прозрачен для django-admin - чтобы все работало, потребуется переопределять формы всех AdminModel в которых нужен ввод чисел с плавающей точкой. А во-вторых, вывод значения поля будет по прежнему с точкой. На данный момент меня такое решение устраивает, однако я попробую придумать что-то более гибкое.

Новый WYSIWYG

Ну вот, переделал django-приложения для работы с WYSIWYG-редакторами. Первоначальная реализация была достаточно кривая, к тому же давала возможность работать только с FCKEditor 2, который уже достаточно устарел. Кроме того, после некоторых оптимизаций работы nginx, она перестала работать =/ Новое приложение уже более корректно, хотя еще тоже не доделано. На данный момент еще не реализована нормальная работа с https, однако это проблема не только данного приложения, но и всей системы в целом.

От использоваия FCK для себя я отказался в пользу TinyMCE. Но в самом приложении я реализую постепенно поддержку минимум 3-4х различных редакторов, после чего выложу это все в open-source. Увы, пока мне приходится все это делать одному. Сейчас я перешел к более-менее активному поиску помошника по django (нужно по работе), но насколько быстрым и успешным будет этот поиск не знаю. Сначала я очень понадеялся на проект http://djangopeople.net, но увы... Как оказалось большая часть москвичей, да и россиян вообще, именно к джанго отношения не имеют. Не совсем мне понятна их мотивация для регистрации на таком сайте, но судя по всему, просто чтобы дополнительно "засветиться"  везде, где могут обитаться потенциальные заказчики. Грустно...

PS: начинаю морально готовиться к тому, что сотрудников надо будет искать не в нашей стране.

Хэширование имен загружаемых файлов в Django, часть вторая

В прошлой реализации хранилища файлов с хэшированием имен файлы были доступны клиентам сайта по хэшированному имени, что не всегда удобно — файлы, передаваемые клиентам сайта, не имели осмысленных имён. Решая эту проблему, все хранилище было переработано и теперь представляет собой приложение. Кроме того, требуется дополнительно настроить frontend-сервер. Для начала несколько слов о самом принципе работы системы:
 Схема работы Django SHA1Storage:
  • 1. Клиент запрашивает файл у веб-сервера (frontend, в нашем случае это nginx).
  • 2. nginx перенаправляет запрос к Django (backend), согласно параметрам локации (location), которой соответствует URI запрошенного файла.
  • 3. Django принимает запрос, обрабатывает URL и возвращает nginx'у ответ с кодом 200, содержащий реальный путь до файла (в заранее заданной локации) в заголовке X-Accel-Redirect, а также тип содержимого файла в заголовке Content-type, основанный на расширении изначального имени файла.
  • 4, 5, 6. nginx обрабатывает путь в X-Accel-Redirect и в ответ на исходный запрос (пункт 1) клиента передаёт ему файл из директории, соответствующей локации, если файл найден. Заголовок Content-type, полученный от Django, при этом сохраняется.
Теперь обо всем по порядку. В первую очередь настроим frontend:
    server {
    listen mysite.ru:80;
    server_name mysite.ru www.mysite.ru;

# Нам совершенно не обязательно проксировать все запросы.
# Поэтому ограничиваемся проверкой запросов вида
# http://mysite.ru/media/upload/some_file.ext

# Передаём изменённый GET-запрос Django. В ответ мы должны получить ответ с
# заголовком X-Accel-Redirect.
    location /media/upload {
        rewrite ^/media/upload/(.*) /nginx/?filename=upload/$1 break;

        fastcgi_pass unix:/path/to/django's/fcgi.sock;
        include /etc/nginx/fastcgi_params;
    }
 

# Эта локация отражает реальный путь до директории хранилища наших файлов.
# Именно сюда Django будет ссылаться в X-Accel-Redirect.
# Прямой доступ к файлам клиентам сайта запрещён (для этого нужна директива internal).
    location /sha1storage {
        internal;
        alias /path/to/project/media/sha1storage;
    }

# Запрещаем  клиентам сайта прямые запросы к Django в локацию /nginx
    location /nginx {
        internal;
    }
 

# Запрещаем клиентам сайта прямой доступ к файлам хранилища в обход Django
    location /media/sha1storage {
        internal;
    }

# Локация с файлами, доступными для загрузки клиентам сайта не из sha1storage.
# Сюда Django перенаправит запрос посредством X-Accel-Redirect, если не
# найдёт файл в sha1storage.
    location /upload {
       internal;
       alias /home/www/www51/betalabs.ru/media/upload;
    }
}
Как это все в итоге работает: nginx получает запрос от клиента. Если путь запрашиваемого файла начинается с /media/upload/, то делается запрос к Django по адресу вида '/nginx/?filename=upload/filename.ext', с передачей в GET-переменной filename пути до файла, с указанием изначального имени. Обратите внимание, что в запросе к Django обрезается /media/, поскольку Django всегда работает с файлами внутри директории, указанной в MEDIA_ROOT.
 
Для настройки django нам понадобиться приложение SHA1Storage.
 
ВНИМАНИЕ. На данный момент приложение находится в стадии beta-тестирования и имеет некоторые ограничения в настройке, которые, я надеюсь, в ближайшее время будут ликвидированы. Если вы найдете какие-то баги, буду очень благодарен за багрепорт.
 
Для установки добавляем его в INSTALLED_APPS:
 
INSTALLED_APPS = (
    'sha1storage',
)

# Если хотите использовать это хранилище как основное, можно указать параметр DEFAULT_FILE_STORAGE.
DEFAULT_FILE_STORAGE = 'sha1storage.storage.SHA1FilenameFileSystemStorage'
 
Если вы хотите использовать хранилище только для отдельных файлов, то к полю файла в модели необходимо добавить свойство storage.
 
В urls.py необходимо добавить обработку URL обработчика sha1storage:
urlpatterns = patterns('',
 (r'nginx/', 'sha1storage.views.get_real_filename'),
);
В принципе это все. Теперь при загрузке файла его имя будет хэшироваться, и он будет сохраняться в папку /path/to/project/media/sha1storage/upload_to_mode_field_path/. При этом модели будет передаваться не хэш от имени файла, а само имя. Из дополнительных особенностей: все файлы, кроме стандартной графики, sha1storage возвращает с заголовком Content-Disposition: attachment, в качестве полумеры для закрытия XSS-уязвимостей, пока не реализованы более строгие алгоритмы защиты.
jQuery, :hidden и тэг option

В jQuery имеет место одна проблема с псевдоцсс селектором :hidden - в браузерах отличных от FireFox конструкция вида
 
<script>
$(document).ready(function() {
	$("*:hidden").remove();
});
</script>
 
удалит не только действительно невидимые объекты, но и все options.
Проблема кроется в текущей реализации алгоритма определения видимости:
Sizzle.selectors.filters.hidden = function(elem){
	return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};
 
Фаерфокс этот момент обрабатывает вполне корректно - в качестве габаритов option он всегда возвращает размеры отрисованного select. А вот остальные браузеры (возможно не все конечно, но основные) сплоховали - всегда возвращают 0, что и приводит jQuery в замешательство.
 
В принципе есть два варианта решения данной проблемы. Первый сгодится если проблемы возникают только с вашим кодом - тут можно просто изменить конструкцию селектора:
 
<script>
$(document).ready(function() {
	$("*:hidden:not(option)").remove();
});
</script>
 
Хуже если эта проблема мешает работе каких-либо плагинов. Решить ее можно "подменив" селектор ":hidden", что, между прочим, совсем не сложно.
 
<script>
// Удалять настоящий селектор не за чем.
// Просто переименуем его в :realhidden.
jQuery.expr[':'].realhidden = jQuery.expr[':'].hidden;
// А на его место поместим наш собственный, с дополнительной проверкой
jQuery.expr[':'].hidden = function(elem) {
	// Если обрабатываемый элемент это option возвращаем false
  	if (elem.nodeName == "OPTION") {
		return false;
  	} else { // Если нет - передаем управление оригинальному селектору :hidden 
 		return jQuery(elem).is(":realhidden");
	}
};
$(document).ready(function() {
	// Сработает функция jQuery.forceHidden и все опции останутся на месте
	$("#test :hidden").remove(); 	
});

</script>
 
Главное, чтобы замена селектора произошла до выполнения кода скрипта, который будет ее использовать. Скорее всего достаточно того, что он выполняется до события $(document).ready, но в некоторых случаях могут быть варианты - просто будте внимательный.
jQuery abeInline plugin

Я наконец оформил одну из функцию, которую написал в процессе разработки этого блога, в виде плагина к jQuery. Получился плагин abeInline - средство для inline редактирования контента. Конечно, пока она еще не полная, так сказать beta-версия, но в принципе работоспособная. Сейчас она умеет конвертировать блоки в text input и в textarea, отправлять результат по AJAX и обрабатывать несколько разных блоков как единую форму.
 
Документация - тоже пока не полная =(
Скачать - пока выложил только на jQuery, но планирую сделать проект на code.google.com с ропозиторием и багтреком.
Лицензия - библиотека распространяется по Лицензии BSD.
 
Если честно, это первая библиотека открываемая мной публично. У меня есть одна черта, которая мне очень мешает в вопросе выкладывания своих наработок - мне все время кажется что работа настолько незакончена, что выкладывать ее неприлично. Помимо всего прочего, это распространяется и на запуск личных проектов - многие я не запустил именно потому, что они казались мне не готовыми. Сейчас я усердно борюсь с этими своими ощущениями - в конце концов, полевые испытания всегда самые эффективные. А если что-то в библиотеке не работает - никто же не отменял багтрекинг, патчи и новые версии. Вобщем, теперь я буду стараться выкладывать больше своих наработок - возможно кому-то что-то из них поможет.
Подсветка синтаксиса, часть четветрая, заключительная

Ну вот , кажется, и походит к концу моя эпопея с подсветкой синтаксиса. В процессе беседы, несколько для меня неожиданной, с Иваном Салагаевым, получил от него несколько разъяснений по его библиотеке и сам покопался в ней поглубже. В итоге, убрал из функции инициализации автоматический поиск блоков для подсветки и открыл функцию highlight для внешнего доступа. Кое-какие недочеты конечно остались, но я думаю разберусь со временем. Ивану огромное спасибо за библиотеку и за помощь.
 
Еще добавил вывод номеров строк, но он работает независимо от основной библиотеки.
Подстветка синтаксиса, часть третья

Задумался о том, что в текущей реализации подсветки кода я могу столкнуться с чрезмерным увеличением количества AJAX-запросов. Учитывая что на одной странице выводится до 20 записей, в каждой из которой может оказаться по несколько блоков кода, это чревато десятками запросов.
 
В сети я нашел несколько скриптов для подсветки синтаксиса на JavaScript, однако, после нескольких экспериментов, я убедился что они не дают никаких реальных преимуществ. Самым качественным решением, из тех которые я нашел, является библиотека Ивана Салагаева (http://softwaremaniacs.org/soft/highlight/). Но, во-первых, упакованная библиотека со всеми языками весит 78 Килобайт, а во-вторых, имеет несколько странную реализацию - подсветка осуществляется ТОЛЬКО автоматически, основываясь на анализе кода, то есть нельзя вручную указать какой блок подсвечивать. Скорость его работы я не анализировал, поскольку для меня достаточным для отказа фактором стал именно вес библиотеки.
 
Поэтому я несколько оптимизировал свою систему. В новой версии все блоки на странице обрабатываются одним AJAX запросом. Для этого сначала генерируется форма, в которую агрегируются все блоки с кодом. Форма создается по правилам Django Formset, для удобства ее обработки на сервере.
Полученный от сервера ответ разбит на блоки, которые очень легко разбираются и размещаются где положено.
 
$(document).ready(function() {
	var form = $("<form>"); // Создаем форму.
	var i = 0;
	$(".highlight").each(function() { //Перебираем все блоки требующие подсветки
		var hObject = $(this);
		hObject.attr("id", "highlight-"+i); // Присваиваем блоку уникальный id, чтобы потом найти его.
		// Создаем поле language для формы. Содержит название языка.
		language = $("<input>").attr({
			"name" : "form-"+i+"-language",
			"value" :  hObject.attr("lang")? hObject.attr("lang") : "text", // Если аттрибудет lang не задан, код будет возвращен без подсветки.
			"type" : "text"
		});
		// Создаем поле id для формы. Используется для идентификации блока с результатом.
		id = $("<input>").attr({
			"name" : "form-"+i+"-id",
			"value" :  i,
			"type" : "text"
		});
		// Создаем поле code для формы. Содержит код для подсветки.
		code = $("<textarea>").attr({
			"name" : "form-"+i+"-code",
		}).text(hObject.html());
		// Добавляем поля к форме.
		form.append(language);
		form.append(code);
		form.append(id);
		i++;
	});
	// Если есть хотя бы один блок
	if (i) {
		// Создаем поле form-TOTAL_FORMS. Нужен для Django Formset для обработки формы. 
		total = $("<input>").attr({
			"name" : "form-TOTAL_FORMS",
			"type" : "text",
			"value" : i
		});
		// Создаем поле form-INITIAL_FORMS. Нужен для Django Formset для обработки формы. В нашем случае всегда 0.
		initial  = $("<input>").attr({
			"name" : "form-INITIAL_FORMS",
			"type" : "text",
			"value" : "0"
		});
		// Добавляем поля к форме.
		form.append(total); 
		form.append(initial);
		// Отправляем запрос 
		$.post("/tools/highlight/", form.serialize(), function(response_data) {
			// Перебираем блоки ответа.
			$(response_data).find(".highlighted").each(function(){
				// Заменяем исходный блок, на подсвеченный, ореинтируясь на id.
				$("#highlight-"+$(this).attr("id")).replaceWith($(this).html());
			});
		});
	}
});
Подсветка синтаксиса, часть вторая

Мои эксперименты так важны, так прекрасны, так удивительны, что я с трудом могу оторваться от них, чтобы поесть.
(с) Н. Тесла

Как я и думал, встраивание подсвеченного кода в пост в чистом виде оказалось решением скверным. Причем, помимо неудобств при редактиваронии, такой подход вызывает дополнительные проблесмы при экспотре RSS - лишние теги + лишние стили... ничего хорогошего вобщем. =)
 
В итоге я заменил способ подстветки - теперь это работает так:
 
В WYSIWYG вставляется контейнер с небработанным кодом, которому присваивается класс highlight и в аттрибуте lang указывается язык для подсветки. По окончании загрузаки страницы, из всех контейнеров класса highlight извлекается их содержимое и AJAX-запросом отправляется на сервер, а полученный результат замещает собой первоначальный контейнер.
 
Выполнено это пока не очень изящно, однако работает.
 
$(document).ready(function() {
	$(".highlight").each(function() {
		var hObject = $(this);
		$.post("/tools/highlight/", {
			"language" : hObject.attr("lang")? hObject.attr("lang") : "text",
			"code" : hObject.html()
		},
		function(response_data) {
			hObject.replaceWith($("<div>").append(response_data));
		});
	})
});
Хэширование имен загружаемых файлов в Django

Уже давно я столкнулся в Django с проблемой загрузки файлов, с utf8 символами в названии. При сохранении такого файла django рушилась с руганью на ошибку конвертации ascii строки. Однако тогда разбираться с проблемой я не стал за ненадобностью - мне было проще переименовывать редкие файлы с utf8 именами.
 
А вот сейчас проблема эта всплыла опять и достаточно остро. За советом как лучше поступать с utf8 в именах файлов я обратился к хорошему сисадмину, который посоветовал мне вообще отказаться от сохранения юникода. Это чревато рисками обхода ограничений директории и получения доступа к исполняемым файлам системы.
 
"Дело в том, что из-за отсутствия гарантий однообразности восприятия специальных символов в имени файла, одни функции в системе могут счесть имя файла валидным и безопасным, а другие - иначе интерпретировать некоторые символы, в результате чего есть опасность нарваться на "../../../filename" в аргументах системному вызову, со всеми вытекающими." © Arach
 
Поэтому, было решено просто хэшировать имена загружаемых файлов. Есть в этом конечно и минус (при скачивании файла несколько не удобно будет его идентифицировать), однако на данном этапе так будет лучше. А уж в процессе подумаю о решении и этой проблемы.
 
Изменить метод работы с файлами в Django очень просто - достаточно сделать специальный обработчик - подкласс django.core.files.storage.Storage. Мало того, в случае лишь частичной замены методов работы, можно наследовать стандартные дочерние классы.
 
# -*- coding: utf-8 -*-
from django.core.files.storage import FileSystemStorage
from hashlib import sha1

class SHA1FilenameFileSystemStorage(FileSystemStorage):
	def get_valid_name(self, name):
		type=""
		if name.rfind("."):
			type = name[name.rindex("."):]
		return str(sha1(name.encode("UTF-8")).hexdigest())+type
 
В данном случае я наследовал стандартный класс хранилища файлов FileSystemStorage, переопределив в нем всего один метод. Метод get_valid_name отвечает за генерацию валидного имени файла ДО проверки доступности такого имени для сохранения. Сначала мы пытаемся отделить расширение файла, если таковое есть, после чего хэшируем входящее имя и прибавляем к нему расширение. Лично я предпочел установить этот обработчик в качестве стандартного в своих приложениях, для чего в settings.py определяется переменная DEFAULT_FILE_STORAGE.
 
import django_useful
DEFAULT_FILE_STORAGE = 'django_useful.sha1storage.SHA1FilenameFileSystemStorage'
Если же надо применить данный обработчик только к определенным файлам, нужно в модели передать путь  его в качестве аргумента storage
 

from django.db import models
import django_useful

class Car(models.Model):
    photo = models.ImageField(storage=django_useful.sha1storage.SHA1FilenameFileSystemStorage(location='/media/photos'))
 
PS: Сегодня добавил в блог подсветку синтаксиса с помощью Pygments. Реализовал в виде плагина для FCKEditor, но пока не очень изящно - код со стилями и прочей ересью вставляется прямо в пост. Не очень удобно редактировать - фактически приходится заново генерировать код полностью. Думаю что лучше будет заменить это все на вставку некого плейсхолдера, и генерировать код непосредственно перед выводом, с помощью PreView триггера.
Pisa XHTML2PDF и поддержка HTTP AUTH

В рамках Торговой Системы понадобилось мне сделать генератор этикеток и ценников. Мой старый, генерировавший jpg картинки по строгому шаблону не годится, потому что информация на разхных ценниках разная, сответсвтенно и размеры у них должны быть динамические. Наиболее быстрым и удобным решением, мне показалось генерировать pdf. В процессе размышлений и поиска, была найдена библиотека Pisa, конвертирующая html в pdf. Вот это совсем то что нужно. Само собой есть определенные ограничения в верстки, но главное она понимает большую часть html+css.
 
Все было неплохо, пока не наткнулся на 2 момента. Момент первый - если картинка вставляется в ячейку таблицы, у которой задан конкретный размер, то картинка эта отображается только в том случае, если у нее заданы аттрибуты width и height. То есть встраивать картинки с неопределенным размером в таблицу не получается. Поиски по комьюнити ничего не дали, поэтому полез в исходники. В итоге обнаружилось следующее - в методе определяющем парметры картинки, в случае отсутствия жестко прописаных размеров, основным источником размеров почему-то считался родительский объект. То есть, если у картинки не заданы размеры, то Pisa пытеатся взять эти размеры у контейнера в котором этак картинка лежит. Если размеры родительского контейнера не прописаны в стилях, то все проходит более-менее нормально - Pisa, не получив от родительского объяекта объявления стиля вычисляет размеры картинки исходя из самой кратинки. А вот, если у родительского контйнера в стиля присутствует width или height, то тогда размеры картинки вычисляются именно их этих параметров. Причем делается это без конвертации значений, из-за чего, собсвтенно и существует проблема. Поскольку, в стилях размеры пишутся с указанием единиц измерения (в моем случае ячейка имела ширину 7cm), заначение это строковое. И естественно, умножение его на 96 dpi (именно так происходит "вычисление" размера картинки), естественно дает на выходе 0. Патчить это я не стал - решил ограничиться вычислением размеров картинки при генерации html шаблона, и указанием их в аттрибутах тега <img> - ИМХО, это наиболее верное решение.
 
А вот вторую проблему без патча решить не удалось. Связана она опять-таки с картинками. Pisa поддерживает два варианта обращения к картинкам - либо по абсолютному пути на сервере (для локальных файлов, естественно), либо по http или https. Однако при этом не была учтена особенность, что урл может быть защищен http-авторизацией. У меня все dev-площадки всегда закрыты от внешнего доступа. Да и некоторые рабочие системы тоже. Поэтому, снова пришлось лезть в исходники, но на этот раз патчить все-таки пришлось. Патч обеспечивает обработку URL вида protocol://userid:passwd@host/path/to/file. То есть перед тем как запросить с сервера host файл /path/to/file система посылает Basic Auth заголовок с логином и паролем для доступа.
 
Патч сделан для версии XHTML2PDF / pisa 3.0.32
 
PS: Пока копался в коде пизы, понял что в ближайшем будущем буду от нее отказываться - не устраивает архитектра и перегруженость. Слишком много не нужного в рамках проекта ТС функционала.
Новый день, новый шаг

"Ну и вопрос! Это, знаешь, как у верблюда спросили:
«Почему у тебя шея кривая?» Так он ответил: «А что у меня прямое?»"
(с) Богдан Архипович Гайдай
(с) Волны гасят ветер
(с) А. и Б. Стругацкие

Сегодня добавил в блог базовую регистрацию. Зарегистрироваться в системе теперь можно, но вот редактирование профиля пока не работает. Параллельно с этим, добавил поддержку комментариев, тоже в самом простом варианте пока - без форматирования.
 
Никак не могу решить, нужна ли в комментариях поддержка ответов - построение древовидной структуры. В некоторых блогах это есть, а в некоторых нету. Количество плюсов-минусов для каждого варианта лично для меня представляется примерно одинаковым, хотя возможно я чего-то не понимаю. В любом случае комментирование подлежит капитально доработке, а начать ее надо, как мне кажется, с оформления комментариев. Уж больно убого сейчас они смотрятся.
Hello World!

"- Вылупился, - спокойно сказал Роман, глядя в потолок.
- Кто? - Мне было не по себе: крик был женский.
- Выбегаллов упырь, - сказал Роман. - Точнее, кадавр."
(с) А. и Б. Стругацкие

Итак, пришло время перейти от лабораторных изысканий к полевому тестированию. Тестировать мы будем разнообразные творения моего сомнительного гения, но начнем, собственно, с блогового движка AbendBlatt, создаваемого на базе фреймворка Django. О самом фреймворке, а вернее о моих впечатлениях от него мы поговорим позже, а пока я вкратце опишу что представляет из себя на данный момент AbendBlatt.
 
Итак, для начала несколько слов об архитектуре:
 
AbendBlatt это многопользовательский блог имеющий несколько уровней пользователей:
  • Администратор - пользовател обладающий абсолютно всеми правами. В данном случае это я.
  • Авторы - пользователи, имеющие возможность публиковать записи.
  • Читатели - могут читать все записи, комментировать их, участвовать в опросах ну и все такое прочее.
Записи блога подразделяются на Колонки aka Категории. Корме общих категорий, каждый Автор может вести собственную, Авторскую Колонку.
 
Единственное - на данный момент регистрация еще не работает. =)
 
Далее. Одна из особенностей блога это обработчики записей. Их есть два типа - PostPost и PreView.
PostPost-обработчики срабатывают при публикации записи. В первую очередь, они созданы для поддержки кросспостинга.
PreView-обработчики переваривают записи перед их выводом на экран. Конвертиры смайлов, контекстные анализаторы и так далее.
 
Блог снабжен системой тегов для сообщений. Она реализована благодаря приложению django-tagging.
 
Ну и все - что еще можно рассказать о системе в данный момент я не знаю, так что все остальное буду описывать в процессе. Вряд ли, конечно, кто-то в ближайшее время вообще найдет этот блог, но я пока и не очень хочу чтобы его активно находили, так что кросспостинг-обработчик (пока реализован только кросспостинг в LiveJournal) отключен. Сначала надо доделать интерфейсы регистрации, профиля пользователя и комментирования. После этого, думаю, кросспостинг уже можно будет включить, ну и ждать гостей.

Авторизация

Логин:

Пароль:


Регистрация | Забыли пароль?


Последние записи


Promo

Follow pyhoster on Twitter Subscribe

Реклама

A Django project.