В каждом новом веб-проекте встает дилемма - использовать ли wysiwyg в административной части сайта. С одной стороны хочется избавить себя от будущего утомительного контент-менеджмента, который не очень дорого оплачивается (если вообще оплачивается), а с другой - дашь пользователю полноценный wysiwyg - он вмиг сломает всю верстку и в итоге тебя же и обвинит: "ты мол программист, ты должен был все учесть"...
Каждый раз приходится анализировать, что за пользователи будут рулить сайтом, обучать даже иногда.
В общем это проблема не новая. Есть еще одна - смежная с ней. Часто начинающие "контент-менеджеры" сайтов (они же и их владельцы по-совместительству :) не брезгуют простым копипастом контента с сайтов конкурентов, сайтов с понравившимся контентом.. Им невдомек, что есть понятие "копирайт", что редактор "не умеет" превращать чужие картинки в свои, что он копирует буквально все, в том числе и внешние ссылки.
Один такой мой клиент, пользующийся интернет-магазином на django-lfs, ну никак не хотел бросать такую вредную привычку , пришлось запилить ему логику дайнлоада картинок из скопированного куска, вычистку непредусмотренных внешних ссылок .
Везде , где можно был переопредлен обработчик save моделей и в этом обработчике вызывался метод, который скачивает картинки в media, меняет пути на свои локальные, чистит внешние ссылки.
Планирую еще и PIL прикрутить к этому делу, чтобы вотермарки накладывать. Любой каприз, как говорится, за ваши деньги.
p.s. но тырить контент с чужих сайтов, не оставляя ссылок - нехорошо , о чем все мои клиенты честно предупреждаются.
p.p.s если кого интересует, то могу код причесать и выложить на какой-нибудь bitbucket - пишите.
пятница, 29 марта 2013 г.
суббота, 23 марта 2013 г.
Хостинг jino и bitbucket
Не хочу давать ни рекламы , ни антирекламы , но у меня возникает следующая проблема с хостингом jino.
Клонирование репозитория с bitbucket в данный момент невозможно, так как hg clone отваливается по tcp-таймауту. Проблема перманентная, то есть не связана с какими-то временными трудностями на любой из сторон.
Вариант с клонированием через ssh не проходит, так как на jino нет ssh-клиента.
Вот и приходится обновлять исходники через третий сервер (
Саппорт пока не ответил на просьбу устранить эту проблему или вообще объясить как-то причину .
Ждем.
Клонирование репозитория с bitbucket в данный момент невозможно, так как hg clone отваливается по tcp-таймауту. Проблема перманентная, то есть не связана с какими-то временными трудностями на любой из сторон.
Вариант с клонированием через ssh не проходит, так как на jino нет ssh-клиента.
Вот и приходится обновлять исходники через третий сервер (
Саппорт пока не ответил на просьбу устранить эту проблему или вообще объясить как-то причину .
Ждем.
понедельник, 4 марта 2013 г.
lightbox vs elastislide
В одном из веб-проектов потребовалось реализовать галерею изображений.
Ранее уже успешно использовал плагин jquery.elastislide.js.
Применил и на этот раз.
Кроме того, надо было сделать так, чтобы по клику на фотку она открывалась в "полный рост" - наиболее популярным решением сейчас является для этого lightbox , его я и попытался применить.
Все сделал по манам, верстка правильная, ошибок в консоли js нет, но изображение по клику не открывается - lightbox не фурычит.
Сразу заподозрил конфликт обработчиков событий. Так и оказалось.
Чтобы его преодолеть в опциях создания слайдера необходимо обнулить обработчик onClick изображений:
Ранее уже успешно использовал плагин jquery.elastislide.js.
Применил и на этот раз.
Кроме того, надо было сделать так, чтобы по клику на фотку она открывалась в "полный рост" - наиболее популярным решением сейчас является для этого lightbox , его я и попытался применить.
Все сделал по манам, верстка правильная, ошибок в консоли js нет, но изображение по клику не открывается - lightbox не фурычит.
Сразу заподозрил конфликт обработчиков событий. Так и оказалось.
Чтобы его преодолеть в опциях создания слайдера необходимо обнулить обработчик onClick изображений:
$('#carousel').elastislide({imageW :300,margin: 30,minItems: 2, onClick: null});воскресенье, 24 февраля 2013 г.
Небольшая ремарка по django-imagestore
Тому, кто хочет организовать фотогалерею на django, могу посоветовать в качестве основного приложения django-imagestore .
Приличный функционал, возможность расширения засчет переопределения моделей альбомов и фотографий, более-менее удобная админка.
Но как всегда не без греха. Как минимум одна проблема в этом модуле есть - а именно проблема каскадного удаления..
Допустим, у вас есть альбом, в нем много фотографий.
Вы из админки удаляете фотографию (не выключаете из альбома, а просто удаляете).
Мелким шрифтом , конечно, показывается предупреждение о том, что будут удалены все сущности. Но кто их читает ?)
Вот и получается - хотели удалить фотку, а удалили весь альбом.
А если на альбом ссылалась еще какая-нибудь сущность, то и она успешно удалится.
На подобное я напарывался в django-lfs.
Чтобы решить эту проблему , необходимо либо пропатчить поле Image.album, добавив в него on_delete=models.SET_NULL.
Либо переопределить Image , как это позволяет делать imagestore и уже аналогично решить эту проблему на уровне своей модели.
Приличный функционал, возможность расширения засчет переопределения моделей альбомов и фотографий, более-менее удобная админка.
Но как всегда не без греха. Как минимум одна проблема в этом модуле есть - а именно проблема каскадного удаления..
Допустим, у вас есть альбом, в нем много фотографий.
Вы из админки удаляете фотографию (не выключаете из альбома, а просто удаляете).
Мелким шрифтом , конечно, показывается предупреждение о том, что будут удалены все сущности. Но кто их читает ?)
Вот и получается - хотели удалить фотку, а удалили весь альбом.
А если на альбом ссылалась еще какая-нибудь сущность, то и она успешно удалится.
На подобное я напарывался в django-lfs.
Чтобы решить эту проблему , необходимо либо пропатчить поле Image.album, добавив в него on_delete=models.SET_NULL.
Либо переопределить Image , как это позволяет делать imagestore и уже аналогично решить эту проблему на уровне своей модели.
воскресенье, 17 февраля 2013 г.
Слайды: кастомизация интерфейса django-admin
На просторах интернета попалась следующая презентация, которая может задать вектор начинающему джанговоду в направлении того, как можно изменить стандартный интерфейс админки django.
Не могу не поделиться
Не могу не поделиться
вторник, 12 февраля 2013 г.
How to improve Test::Class to conrol test ttl
To make Test::Class control test-suites time-to-live you could inherit Test::Class and redefine runtests method..
For example:
I think, we don't need SIGCHILD here..
source
For example:
package TTL::Test::Class;
use base qw(Test::Class);
use Test::More;
use POSIX ":sys_wait_h";
use constant DEFAULT_TIMEOUT => 60;
# переопределяем метод запуска тестов
sub runtests{
my $t = DEFAULT_TIMEOUT;
my $pid = fork;
my $child_ret_code;
if( $pid > 0 ){ # parent process
#child waiting loop
my $ok_flag = 0;
for(my $i=0; $i < $t; $i++){
if(waitpid($pid, WNOHANG)){
$child_ret_code = $?/256;
ok($child_ret_code);
$ok_flag = 1;
last;
};
sleep(1);
};
if( not $ok_flag ){
diag "Killing test by its timetolive...";
kill TERM =>
$pid;
return $ok_flag;
};
return $child_ret_code;
} elsif( $pid == 0 ) { #child
exit $self->SUPER::runtests;
} elsif ( not defined($pid) ) { #unsuccessfull fork
die "Cannot fork child process: $!";
}
}
1;
I think, we don't need SIGCHILD here..
source
вторник, 5 февраля 2013 г.
django-sitetree: key error 'request'
Сообщение об ошибке при работе с django-sitetree в админке django :
говорит о том , что необходимо добавить
"django.core.context_processors.request", в TEMPLATE_CONTEXT_PROCESSORS
только и всего
говорит о том , что необходимо добавить
KeyError at /admin/sitetree/tree/1/
'request'
| Request Method: | GET |
|---|---|
| Request URL: | http://localhost:8080/admin/sitetree/tree/1/ |
| Django Version: | 1.4.3 |
| Exception Type: | KeyError |
| Exception Value: | 'request' |
"django.core.context_processors.request", в TEMPLATE_CONTEXT_PROCESSORS
только и всего
четверг, 31 января 2013 г.
django-lfs: EOFError при работе в режиме кеширования
На одном моем сервере "крутится" магазинчик под управлением Django-lfs
EOFError
TRACEBACK:
File "..../ pydocs/env/lib/python2.7/site-
packages/Django-1.4.1-py2.7. egg/django/core/handlers/base. py", line 111, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "..../ pydocs/deploy/../lfs/catalog/ views.py", line 267, in category_view
inline = category_products(request, slug, start)
File "...../ pydocs/deploy/../lfs/catalog/ views.py", line 358, in category_products
temp = cache.get(cache_key)
File "....../ pydocs/env/lib/python2.7/site- packages/Django-1.4.1-py2.7. egg/django/core/cache/ backends/db.py", line 75, in get
return pickle.loads(base64. decodestring(value))
Ошибка "мигающая".
Пока лишь понятно, что копать в сторону python модуля pickle .
Поверхностный анализ кода этого pickle пока не помог в поиске причины.
Ждет меня дебаг продолжительный, судя по всему :)
- django 1.4
- nginx 1.1.9
- uwsgi 1.2.3
EOFError
TRACEBACK:
File "..../
response = callback(request, *callback_args, **callback_kwargs)
File "..../
inline = category_products(request, slug, start)
File "...../
temp = cache.get(cache_key)
File "....../
return pickle.loads(base64.
Ошибка "мигающая".
Пока лишь понятно, что копать в сторону python модуля pickle .
Поверхностный анализ кода этого pickle пока не помог в поиске причины.
Ждет меня дебаг продолжительный, судя по всему :)
четверг, 27 декабря 2012 г.
django-lfs: грабли с каскадным удалением
Текущая известная мне версия django-lfs писалась для django версий еще младших, чем 1.3. До этой версии включительно ORM этого фреймворка содержал досадную "особенность" - ненастраиваемое каскадное удаление сущностей , ссылающихся на удаляемый объект.
Кто не знал этой особенности, получал граблями по лбу либо при тестировании, либо уже, что тоже бывало , при эксплуатации.
Классический фейл с django-lfs: оператор интернет магазина решает удалить производителя, ставшего ненужным. На этот момент к производителю были привязаны пара сотен товаров.
Manage-интерфейс lfs настолько дружелюбен, что не предупреждает юзера об удалении этих пары сотен товаров, заодно с удалением производителя. Но ведь так можно и до инфаркта людей довести! ) В общем эдакое "кто не спрятался я не виноват" кто не делает бекап, вбивает все заново))
Более опытные django-воды обходили подобные проблемы либо на уровне БД либо в хуке для delete-операции, явно обнуляя соответствующие поля в ссылающихся таблицах.
Начиная с версии 1.3 появилась возможность настраивать ORM при удалении объекта. Для этого в описании поля модели надо указывать: on_delete=models.SET_NULL
Круто, только вот разрабы lfs об этом не позаботились, так что патчить модели надо самим.
Ну и в завершение.. чтобы избегать подобных граблей:
1. не надейтесь на протестированность сторонних решений, особенно , если они бесплатные
2. постоянно углубляйте знания инструментария, которым пользуетесь .. в данном случае курить djangoproject.com
3. ежедневный бекап БД спасет от большинства подобных фейлов ( а лучше бекапиться несколько раз в день )
воскресенье, 16 декабря 2012 г.
django-lfs: улучшение механизма работы с картинками
В предыдущем посте я упоминал об ошибке, которая возникнет , если пользователь сайта на django-lfs будет загружать картинки с именем файла , длиной более 99 символов.
Пользователей не выбирают, а костыли в виде явного указания немеряного max_length , меня не устраивают.
Я решил сделать так:
1. принимаем картинку
2. запоминаем расширение файла
3. получаем хеш (например md5) от имени + текущее время (чтобы не получать одинаковых хешей для разных картинок с одинаковыми именами)
4. получаем новое имя конкатенацией хеша и расширения
5. сохраняем
Наилучшим местом в коде, куда вклинить этот алгоритм является lfs/core/fields/thumbs.py
Подключаем вспомогательные библиотеки:
Генерируем новое имя файла перед сохранением:
После этого все имена картинок будут одинаковой длины и о том , зачем юзеру такие длинные файлы, можно не думать.
p.s. дальнейшее улучшение механизма работы с картинками будет - проверка файла на то, в допустимом графическом ли он формате или нет.
Сейчас django-lfs принимает на вход все подряд. Судя по всему, безопасности это практически не угрожает , но 404 по запросам картинок - это тоже нехорошо.
Пользователей не выбирают, а костыли в виде явного указания немеряного max_length , меня не устраивают.
Я решил сделать так:
1. принимаем картинку
2. запоминаем расширение файла
3. получаем хеш (например md5) от имени + текущее время (чтобы не получать одинаковых хешей для разных картинок с одинаковыми именами)
4. получаем новое имя конкатенацией хеша и расширения
5. сохраняем
Наилучшим местом в коде, куда вклинить этот алгоритм является lfs/core/fields/thumbs.py
Подключаем вспомогательные библиотеки:
+++ b/pydocs/lfs/core/fields/thumbs.py Sun Dec 16 19:17:04 2012 +0400
@@ -7,6 +7,13 @@
except ImportError:
from PIL import Image
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+from time import time
def save(self, name, content, save=True):
+ parts = name.split('.')
+ ext = '.%s' % parts[-1] if len(parts) > 1 else ''
+ name = md5("%s-%s"%(name.encode('utf-8'), time())).hexdigest()
+ name = "%s%s"%(name,ext)
super(ImageWithThumbsFieldFile, self).save(name, content, save)
Подписаться на:
Сообщения (Atom)