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

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

 

Записи с тэгом Django

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-уязвимостей, пока не реализованы более строгие алгоритмы защиты.
Хэширование имен загружаемых файлов в 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 триггера.
Новый день, новый шаг

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

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

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

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

Авторизация

Логин:

Пароль:


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


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


Promo

Follow pyhoster on Twitter Subscribe

Реклама

A Django project.