додатковий трафік з Google Images

В свій час писав автоматизацію для наповнення інтернет-магазину, яка використовувала пошук в "Google Images". І час від часу натрапляв на сайти, де знаходився потрібний мені малюнок, але я не встигав на нього клікнути чи то скопіювати шлях, як відбувався повний перехід на вищезгаданий сайт з фреймів "Google Images". І перехід не на потрібну мені сторінку, де є малюнок, а в корінь сайту. Велика незручність для кінцевого користувача і додатковий трафік для власнику сайту. Про корисність цього трафіку можна довго сперечатись, але і немає страху втратити цього випадкового відвідувача :)
Технічний момент. В head'і прописуємо:
<script type='text/javascript'>
    if (parent.frames.length > 0) {.
        //parent.location.href = location.href;
        parent.location.href = 'http://t-v.te.ua';
    }
</script>

Закоментований варіант - перехід на сторінку де є малюнок, розкоментований - в корінь сайту.

split на bash'і

snat=(
    55410:192.168.10.99:80
    55415:192.168.10.199:80
    55411:192.168.20.99:80
    55412:192.168.30.199:80
    55413:192.168.24.99:80
    55414:192.168.20.199:80
)

for i in ${snat[@]}; do
    set -- "$i"
    IFS=":"; declare -a Array=($*)

    iptables -A FORWARD -p tcp -s ${Array[1]} --sport ${Array[2]} -j ACCEPT
    iptables -A FORWARD -p tcp -d ${Array[1]} --dport ${Array[2]} -j ACCEPT
    iptables -t nat -A PREROUTING -p tcp -i eth0 --dport ${Array[0]} -j DNAT --to-destination ${Array[1]}\:${Array[2]}

    echo "${Array[@]}"
done
п.с. збоченська мова

Багатопотокова проксі-чекалка на Python

#!/usr/bin/python

import Queue
import threading
import MySQLdb
import urllib2
import time

class DB:
    conn = None
    def connect(self):
        self.conn = MySQLdb.connect(host = "x.y.z.q", user = "user", passwd = "password", db = "database")
    def query(self, sql):
        try:
            cursor = self.conn.cursor()
            cursor.execute(sql)
        except (AttributeError, MySQLdb.OperationalError):
            self.connect()
            cursor = self.conn.cursor()
            cursor.execute(sql)
        return cursor

proxyList = []

global db
db = DB()
curr = db.query("SELECT url FROM proxy")
result_set = curr.fetchall ()
for row in result_set:
    proxyList.append(row[0])

queue = Queue.Queue()

class ThreadUrl(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        while True:
            host = self.queue.get()
            try:
                proxy_handler = urllib2.ProxyHandler({'http':host})
                opener = urllib2.build_opener(proxy_handler)
                opener.addheaders = [('User-agent','Mozilla/5.0')]
                urllib2.install_opener(opener)
                req = urllib2.Request("http://www.proxylists.net/proxyjudge.php")
                sock=urllib2.urlopen(req, timeout= 7)
                rs = sock.read(5000)
                if 'ProxyLists.Net - Proxy judge' in rs:
                    print "ok %s" % host
                    db.query("UPDATE proxy SET checked=NOW() WHERE url = '%s' " % host)

            except:
                print "err %s" % host
                db.query("DELETE FROM proxy WHERE url LIKE '%s' " % host)

            self.queue.task_done()

def main():
    for i in range(20):
        t = ThreadUrl(queue)
        t.setDaemon(True)
        t.start()

    for host in proxyList:
        queue.put(host)

    queue.join()

main()

Django: реалізація динамічного select'у

Не доводилось раніше використовувати динамічний select, тобто select, який через ajax підтягує значення відповідно до значення вибраного в попередньому select'і. Тож довелось розібратись. Пропоную свій код, який не претендує на супер-оригінальність


models.py

# -*- coding:utf-8 -*-

from decimal import Decimal
from django.db import models
from django.db.models import permalink

class Category(models.Model):
    name = models.CharField(max_length=60)
    slug = models.SlugField(max_length=60, unique=True)
    order = models.IntegerField(default=255)
    count_posts = models.IntegerField(default=0)

    class Meta:
        ordering = ['order']

    def __unicode__(self):
        return self.name

class SubCategory(models.Model):
    name = models.CharField(max_length=60)
    slug = models.SlugField(max_length=60)
    parent = models.ForeignKey(Category, null=True, blank=True, related_name='subcategories')
    count_posts = models.IntegerField(default=0)
    order = models.IntegerField(default=255)

    class Meta:
        unique_together = (('parent', 'slug'),)
        ordering = ['name']

    def __unicode__(self):
        return u'%s -> %s' % (self.parent, self.name)

class Post(models.Model):
    DENIED_STATUS = 0
    APPROVED_STATUS = 1
    PENDING_STATUS = 2

    POST_STATUS=(
        (DENIED_STATUS, 'Denied'),
        (APPROVED_STATUS, 'Approved'),
        (PENDING_STATUS, 'Pending'),
    )

    OFFER = 1
    DEMAND = 2
    SERVICES = 3
    RENTAL = 4
    ABSTRACT = 5
    VACANCY = 6

    POST_SUBJ=(
        (OFFER, u'Попит'),
        (DEMAND, u'Пропозиція'),
        (SERVICES, u'Послуги'),
        (RENTAL, u'Аренда'),
        (ABSTRACT, u'Резюме'),
        (VACANCY, u'Вакансії'),
    )

    category = models.ForeignKey(SubCategory)
    subj=models.IntegerField(u'Тип', max_length=1, choices=POST_SUBJ, default=OFFER)
    status=models.IntegerField(u'Стан', max_length=1, choices=POST_STATUS, default=APPROVED_STATUS)
    title = models.CharField(max_length=60)
    slug = models.SlugField(max_length=60)
    body = models.CharField(max_length=250)
    contact = models.CharField(max_length=120)
    email = models.CharField(max_length=100, null=True, blank=True)
    price = models.DecimalField(max_digits=8, decimal_places=2, default=Decimal('0.00'), )
    ip_address = models.IPAddressField(u'IP address', null=True, blank=True)
    youtube = models.CharField(max_length=100, null=True, blank=True)
    site_link = models.CharField(max_length=100, null=True, blank=True)
    posted_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['posted_on']

    def __unicode__(self):
        return u'%s' % self.title

    @permalink
    def get_absolute_url(self):
        return (
            'ogo_ogo', None, {
                'cat_slug':self.category.parent.slug,
                'sub_cat_slug':self.category.slug,
                'id':self.pk,
            })

    def formattedprice(self):
        return u'%01.2f' % self.price

    def save(self, *args, **kwargs):
        super(Post, self).save(*args, ** kwargs)
views.py
@render_to('')
def add(request, form_class=AddPostForm, template="add.html"):
    if request.method == "POST":
        form = form_class(request.POST)
        if form.is_valid():
            new_obj = form.save()
            return redirect(reverse('ogo_add_complete', kwargs={'id':new_obj.id,}))
    else:
        form = form_class()
    return {
        "form": form,
        }, template

@render_json
def fetch_subcat_json(request):
    result = list()

    try:
        cat = Category.objects.get(pk=int(request.GET.get('id', '')))
        sub_cat = SubCategory.objects.filter(parent=cat)

        result.append({ 'optionDisplay': '-- Оберіть категорію --', "optionValue" : '' })
        for sc in sub_cat:
            result.append({ 'optionDisplay': sc.name, "optionValue" : sc.pk })
    except:
        result.append({ 'optionDisplay': '-- Оберіть категорію --', "optionValue" : '' })
    return result
forms.py
# -*- coding:utf-8 -*-

from types import *
from django import forms
from django.utils.translation import ugettext_lazy as _, ugettext
from django.template.defaultfilters import slugify
from apps.ogo.models import Post, Category, SubCategory

class AddPostForm(forms.Form):
    category = forms.ChoiceField(required=True,)
    sub_category = forms.ChoiceField(required=True,)
    type_post = forms.ChoiceField(required=True,)
    title = forms.CharField(label=_(u'Заголовок'), max_length=100, widget=forms.TextInput())
    contact = forms.CharField(label=_(u'Конакт'), max_length=100, widget=forms.TextInput())
    body = forms.CharField(label=_(u"Текст оголошення"),max_length = 350, widget = forms.Textarea, required=True)
    contact = forms.CharField(label=_(u'Конакт'), max_length=100, widget=forms.TextInput())
    price = forms.CharField(label=_(u'Вартість, грн.'), max_length=10, widget=forms.TextInput(),required=True)

    def __init__(self, *args, **kwargs):
        super(AddPostForm, self).__init__(*args, **kwargs)

        self.sub_cat = SubCategory.objects.all()

        CAT_CHOICES = [('', '-- Оберіть --'), ] + [(c.pk, c.name) for c in Category.objects.all().order_by("pk")]
        SUBCAT_CHOICES = [('', '-- Оберіть категорію --'),]
        TYPE_CHOICES = [('', '-- Оберіть --'), ] + [(t[0], t[1]) for t in Post.POST_SUBJ]

        try:
            init_cat = int(args[0]['category'])
            SUBCAT_CHOICES = [('', '-- Оберіть --'), ] + [(c.pk, c.name) for c in SubCategory.objects.filter(parent=init_cat).order_by("pk")]
        except:
            init_cat = None

        try:
            init_sub_cat = int(args[0]['sub_category'])
            SUBCAT_CHOICES = [('', '-- Оберіть --'), ] + [(c.pk, c.name) for c in SubCategory.objects.filter(parent=init_cat).order_by("pk")]
        except:
            init_sub_cat = None

        try:
            init_type = int(args[0]['type_post'])
        except:
            init_type=None

        self.fields['category'] = forms.ChoiceField(choices=CAT_CHOICES, label=u'Категорія', initial=init_cat)
        self.fields['sub_category'] = forms.ChoiceField(choices=SUBCAT_CHOICES, label=u'Підкатегорія', initial=init_sub_cat)
        self.fields['type_post'] = forms.ChoiceField(choices=TYPE_CHOICES, label=u'Тип', initial=init_type)

    def save(self):
        return Post.objects.create(
            category = self.sub_cat.get(pk=self.cleaned_data["sub_category"]),
            subj = self.cleaned_data["type_post"],
            title = self.cleaned_data["title"],
            slug = slugify(self.cleaned_data["title"]),
            body = self.cleaned_data["body"],
            contact = self.cleaned_data["contact"],
            price = self.cleaned_data["price"],
        )
add.html
    

    
{{ form.non_field_errors }} {% for field in form %} {% endfor %}
{{ field.label_tag }}:
{{ field }} {{ field.errors }}
urls.py
# -*- coding:utf-8 -*-

from django.conf.urls.defaults import *

urlpatterns = patterns('ogo.views',
    url(r'^dodaty/', 'add', name = "ogo_add"),
    url(r'^dodano-uspishno/(?P\d{1,5})/$', 'add_complete', name = "ogo_add_complete"),
    url(r'^fetch-subcat/$', 'fetch_subcat_json', name = "fetch_subcat"),
)

Оновлення (RENEW) домену org.ua

Залишу шпаргалку для себе і може для когось.

to: register_query@org.ua
subject: RENEW domain.org.ua

domain:domain.org.ua
descr: domain homepage
admin-c:ваш_нік-UANIC
tech-c: ваш_нік-UANIC
nserver: ns1.ваш_днс
nserver: ns2.ваш_днс
changed: e-mail@e-mail.com 20121003
source: UANIC

програмне (HTTP) перезавантаження китайських ІР-камер

Є ноунеймовська китайська ІР-камера. Постала задача програмно її перевантажувати. Перше що спало на думку - телнет. І він виявився навіть відкритим, але зайти таки не вдалось. Так званий український сапорт сказав що телнет не працює, закрили його китайці. Довелось копати в сторону HTTP.
Отож рішення:

wget --cookies=on \
          --keep-session-cookies \
          --save-cookies cookies.txt \
          --post-data 'A=login&B=password&button=Login' \
          http://x.y.z.x/cgi-bin/LoginAction.cgi
wget --load-cookies cookies.txt \
     -p http://x.y.z.x/cgi-bin/Reboot.cgi

Подібним методом можна автоматизувати не лише камери. FireBug в поміч.

аплоад файлів на Youtube через консоль

Завалялось 80 гіг відео на одному віддаленому сервері. Виявляється, все просто:
# apt-get install python-gdata python-progressbar
Качаємо скрипт: http://code.google.com/p/youtube-upload/
youtube-upload --email=myemail@gmail.com --password=mypassword 
                 --title="A.S. Mutter" --description="A.S. Mutter plays Beethoven" 
                 --category=Music --keywords="mutter, beethoven" anne_sophie_mutter.flv
І що найцікавіше, то можна задавати маску для файлів і одною командою завантажити весь каталог файлів.

“sha256sum mismatch jdk-7u3-linux-x64.tar.gz ” помилка при встановленні Oracle Java

лікується:
sudo rm /var/lib/dpkg/info/oracle-java7-installer*
sudo apt-get purge oracle-java7-installer*
sudo rm /etc/apt/sources.list.d/*java*
sudo apt-get update
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java7-installer

Ubuntu: шрифти в Skype

По замовчуванні вони жахливі. Так як Skype використовує Qt, то доставляємо qt4-qtconfig

apt-get install qt4-qtconfig

запускаємо і налаштовуємо улюблений шрифт

bitbucket: error: RPC failed; result=55, HTTP code = 0

Отримав сьогодні фак від бітбакета:

user@host# git push
Password for 'https://user@bitbucket.org': 
error: RPC failed; result=55, HTTP code = 0
fatal: The remote end hung up unexpectedly
fatal: The remote end hung up unexpectedly
Everything up-to-date

лікується:

git config http.postBuffer 524288000

auto-complete-el - ще одна права рука

Ніколи не думав що так швидко звикну до автокомпліта в консолі. Переставляв ОС на ноуті і відчув себе безруким, коли натиснув Tab після команди і нічого не відбулось.

sudo apt-get install auto-complete-el

Робимо з серії JPEG-файлів відео-файл в Zoneminder

Все просто, r - кількість кадрів за секунду (можна глянути в налаштуваннях камери), -b - бітрейт (якість).

ffmpeg -r 5 -b 1800 -i %03d-capture.jpg out.mp4

після апгрейду Ubuntu, F10 почало викликати контекстне меню

Жахлива незручність, яка нервувала щоразу коли треба було вийти з mc. 5 хвилин гугління і проблема вирішена.
mkdir -p ~/.config/gtk-3.0
cat<<EOF > ~/.config/gtk-3.0/gtk.css

@binding-set NoKeyboardNavigation {
     unbind "<shift>F10"
}

* {
     gtk-key-bindings: NoKeyboardNavigation
}

EOF

щоб mc пам'ятав директорію

Перейшовши з Fedora та Ubuntu стикнувся з незручністю при роботі в терміналі. mc собі пам'ятає свою поточну директорію, а bash свою. Нарешті сьогодні дійшли руки виправити це. Прописуємо в ~/.bashrc стрічку:

source /usr/share/mc/bin/mc.sh

витягуємо погоду з Yahoo Weather на Python'і

import re
import urllib
from xml.dom import minidom
from pprint import pprint
from datetime import datetime

WEATHER_URL = 'http://weather.yahooapis.com/forecastrss?w=%s&u=c'
WEATHER_NS = 'http://xml.weather.yahoo.com/ns/rss/1.0'

def weather_for_location(location_id):
    url = WEATHER_URL % location_id
    dom = minidom.parse(urllib.urlopen(url))
    forecasts = []
    for node in dom.getElementsByTagNameNS(WEATHER_NS, 'forecast'):
        forecasts.append({
            'date': node.getAttribute('date'),
            'low': node.getAttribute('low'),
            'high': node.getAttribute('high'),
            'condition': node.getAttribute('text'),
        })
    ycondition = dom.getElementsByTagNameNS(WEATHER_NS, 'condition')[0]
    ywind = dom.getElementsByTagNameNS(WEATHER_NS, 'wind')[0]
    yatmosphere = dom.getElementsByTagNameNS(WEATHER_NS, 'atmosphere')[0]
    yastronomy = dom.getElementsByTagNameNS(WEATHER_NS, 'astronomy')[0]
    ydescription = dom.getElementsByTagName('description')[1].firstChild.data
    patImgSrc = re.compile('src="(.*)".*/>')

    return {
        'current_condition': ycondition.getAttribute('text'),
        'current_temp': ycondition.getAttribute('temp'),
        'current_humidity': yatmosphere.getAttribute('humidity'),
        'current_visibility': yatmosphere.getAttribute('visibility'),
        'current_sunrise': yastronomy.getAttribute('sunrise'),
        'current_sunset': yastronomy.getAttribute('sunset'),
        'current_wind_speed': ywind.getAttribute('speed'),
        'current_wind_chill': ywind.getAttribute('chill'),
        'current_wind_direction': ywind.getAttribute('direction'),
        'current_img': re.findall(patImgSrc, ydescription),
        'forecasts': forecasts,
        'title': dom.getElementsByTagName('title')[0].firstChild.data,
        'guid': dom.getElementsByTagName('guid')[0].firstChild.data,
        }

генератор ip-адрес на Python

цікавий код :)
from random import randint

def randip():
    while True:
        yield ".".join(str(randint(1, 255)) for i in range(4))

randip().next()

сніфимо HTTP-трафік tcpdump'ом

tcpdump -i eth0  -s0 -A -q 'dst port 80 && src host IP'

чистимо оперативну пам'ять в linux

перелік процесів відсортований по споживанні пам'яті

# ps -eo pid,ppid,rss,vsize,pcpu,pmem,cmd -ww --sort=rss

очищаємо кешовану пам'ять

# sync; echo 3 > /proc/sys/vm/drop_caches

ремонтуємо mySQL

mysqlcheck -Aaco --auto-repair -u root -p

прокидка портів в linux без iptables

socat TCP4-LISTEN:80,fork TCP4:www.yourdomain.org:8080

програми та скрипти для моніторингу та діагностики системи

top
htop
atop
iotop
sysstat: sar iostat
vmstat
ps .....
netstat .....
lsof
fuser
smem
pmap
ps -e -O rss,user | awk '/php[^[]/{sum+=$2}END{print sum}'

простенький скрипт резервного копіювання

#!/bin/bash

DATE=`date +%Y%m%d%H`
BACKUP_DIR=/mnt/backups/backups

dirs=( /mnt/dir1
       /mnt/file1
       /mnt/dir2
)

for i in ${dirs[@]}; do
    tar -cvf $BACKUP_DIR/${i##*/}-$DATE.tar $i
    gzip $BACKUP_DIR/${i##*/}-$DATE.tar
done

find $BACKUP_DIR/* -maxdepth 0 -ctime +15 -type f -exec rm -rfv {} \;

відправка файлу користувачу на скачування

Блін, вбив певно годину часу поки нагуглив. Купа неробочого коду в цьому сегменті.
    file = open(dnldbl.content.path,'r')
    response = HttpResponse(content = file.read(), content_type=dnldbl.mimetype)
    response['Content-Length']      = os.path.getsize(dnldbl.content.path)
    response['Content-Disposition'] = "attachment; filename=%s" % os.path.basename(dnldbl.content.path)
    return response

Видалення старих фоток в zoneminder

Простенький пайтоновський скрипт, який затирає кеш з фотками з 4 дні
#!/usr/bin/python

import MySQLdb
import sys
import shutil

cache='/var/cache/zoneminder/events'

try:
   conn=MySQLdb.connect(host="localhost", user="zmuser", passwd="******", db="zm")
except MySQLdb.Error, e:
   print "Error %d: %s" % (e.args[0], e.args[1])
   sys.exit (1)

cursor=conn.cursor()
cursor2=conn.cursor()

cursor.execute("SELECT * FROM Monitors")
while (1):
   row = cursor.fetchone ()
   if row == None:
       break
   sql = 'SELECT YEAR(StartTime) AS y, MONTH(StartTime) AS m, DAY(StartTime) AS d, DATE_FORMAT(StartTime, "%m") AS mf, DATE_FORMAT(StartTime, "%d") AS df, DATE_FORMAT(StartTime, "%y") AS yf '
   sql += 'FROM Events '
   sql += 'WHERE MonitorId=%s AND StartTime <= DATE_ADD(CURDATE(), INTERVAL -4 DAY) ' % row[0]
   sql += 'GROUP BY m,d; '
   cursor2.execute(sql)
   while (1):
       row2 = cursor2.fetchone()
       if row2 == None:
           break
       directory = "%s/%s/%s/%s/%s/" % (cache,row[0],row2[5], row2[3],row2[4],)
       print directory
       shutil.rmtree(directory)

cursor.close ()
cursor2.close ()
conn.close ()

Танці з бубном навколо керованого комутатора 3COM Baseline 2226 Plus

Попалась б.к. САБЖ-залізяка. Пробував ресетити, курив мануали, пробував знайти ІР і залогінитись, але все безуспішно. Рідний софт-діскавері також не знаходив ІР. Гугл сказав що проблема досить таки глобальна. І найприкріше те, що девайс не має інтерфейсу rs-232. Але десь в коментарях під якоюсь статтею вичитав, що на материнці все ж є колодка, але не виведена на зовні. Отож, зірвав гарантійну лейбу, розкрутив і побачив порт. Знайшов перехідник на якомусь старому залізі і підключив кабель.


 Підключився терміналом і побачив абракадабру. Методом тику підібрав параметри підключення і отримав командний рядок CLI
А далі все просто. Поставив собі потрібну ІР, зайшов на веб-інтерфейс (login: admin; password: <пустий>)

Сподіваюсь може колись комусь пригодиться.

gammu і відсилка sms

Підключаю до Ubuntu по USB свою стару Nokia N85, в менюшці на телефоні обираю Nokia PC Suite. Далі потрібна мінімальна конфігурація gammu

$ gammu-detect
...
[gammu]
device = /dev/ttyACM0
name = Nokia N85
connection = at
...

Пишу цей кусочок у відповідне місце ~/.gammurc або ж за допомогою утиліти gammu-config вписую відповідні дані.

Відіслати SMS можна командою:

$ echo "Тестування СМС розсилки. Тест кирилиці іїє ІЇЄґҐ" | gammu sendsms TEXT +38063ХХХХХХХ -unicode

flask

Якось зайшла мова в одній комюніті про легкий фреймворк на Python для невеличких сайтів.
Ось невеличка презентація. Буде трошки вільного часу, треба буде покурити мануал і потестувати.

Перша оптимізація Django

Закінчив загальну свою першу оптимізацію проекту на Django. Цікаво і круто. Підгадаю, на стартовій сторінці в мене виконувалось більше 300 запитів, від чого я мало не впав і не розчарувався в "Object-relational mapping". Оптимізація з кешуванням зараз дає результат в 30 дуже легеньких запитів і в час неактуальності кешу ще біля десятка, але також легеньких.
Основне зло, з яким довелось боротись:
  •  django-tagging. Дуже корисна і зручна бібліотека міток, але має в post-init два запити і любе звертання до моделі, в якій використовується поле міток множить кількість запитів на 3. Там де мітки не використовуються, лікується через .defer('tags');
  • В мене кілька пов'язаних моделей і використовується в URL два slug з різних моделей. Звісно по документації описав генерацію URL у get_absolute_url і в результаті отримав у вибірках кількість запитів * 2. Так як в мене кешуються ці моделі, то у шаблонах через template tags по ID я вибирав потрібні значення з інших моделей з кешу, в результаті позбувся цього зла;
Додаткове зло - моменти говнокоду, який довелось розібрати по крупинці і сумістити все з кешуванням. На продакшені швидше за все буде використовуватись memcached.

Висновок: Django дуже рулить. Все надзвичайно слухняне + хороша реалізація фреймворку кешування = хороший результат.

оптимізація і django-tagging

Взявся за оптимізацію проджекту, запустив монітор запитів і офігів, на стартовій більше 300 запитів. А виною всього виявився django-tagging, який запускає post-init і на кожен об'єкт отримуємо + 2 додаткових запити. Вирішується досить просто. У запит додаємо defer('tags')

django-tagging і get_by_model з логікою OR

Хороша ліба django-tagging, яка дозволяє тегувати все що завгодно. Вибірку по тегах можна зробити методом get_by_model.
TaggedItem.objects.get_by_model(Post, [u'духовність',u'культура',u'мистецтво'])
і ми отримаємо об'єкти з наступними тегами але з логікою "і". Але мені потрібно такий самий метод, але з логікою "або", тобто вибрати об'єкти де зустрічається хоча би один з перелічених тегів. Мій варіант методу. Трошки говнокод, але працює. Колись може дойдуть руки навести есетичний марафєт.
def get_by_model_or(self, queryset_or_model, tags):
    tags = get_tag_list(tags)
    tag_count = len(tags)
    if tag_count == 0:
        queryset, model = get_queryset_and_model(queryset_or_model)
        return model._default_manager.none()

    queryset, model = get_queryset_and_model(queryset_or_model)
    content_type = ContentType.objects.get_for_model(model)
    opts = self.model._meta # return tagging.taggeditem
    tagged_item_table = qn(opts.db_table) # `tagging_taggeditem`

    where = []
    where.append('%s.content_type_id = %%s' % tagged_item_table,)

    or_string = "("
    or_tuple = ()
    params = [content_type.pk]
    for i in range(len(tags)):
        if i == 0:
            or_string += "%s.tag_id = %%s "
        else:
            or_string += "OR %s.tag_id = %%s "
        or_tuple += (tagged_item_table,)
        params.append(tags[i].pk)
    or_string += ")"

    where.append(or_string % or_tuple)
    where.append('%s.%s = %s.object_id' % (qn(model._meta.db_table),
                                           qn(model._meta.pk.column),
                                           tagged_item_table))

    return queryset.extra(
        tables=[opts.db_table],
        where=where,
        params = params
    )

Django + TinyMCE + Django-tinymce-filebrowser

Як бісить, коли оупенсорсним програмістам впадло писати хоча би мінімальну документацію. Інколи доводиться курити годинами мануали. Цей раз не виключення. Потрібно прикрутити до HTML-едітора файловий браузер (заванаження малюночків на сервер і вставка їх в документ). Одне з легеньких рішень Django-tinymce-filebrowser. Але ні слова не вказано про ініціалізацію цього аппа. Тож:
{% block head_extra %}
    <script src="{{ settings.TINYMCE_JS_URL }}" type="text/javascript"></script>

    <script type="text/javascript">
        tinyMCE.init({
            mode: "textareas",
            theme: "advanced",
            language : 'uk',
            theme_advanced_toolbar_location : "top",
            theme_advanced_toolbar_align : "left",
            theme_advanced_statusbar_location : "bottom",
            theme_advanced_resizing : "True",
            file_browser_callback : "mce_filebrowser"
        });

        function mce_filebrowser(field_name, url, type, win) {
            var cmsURL = "/tinymce-fb/image";    
            if (cmsURL.indexOf("?") < 0) {
                cmsURL = cmsURL + "?type=" + type;
            }
            else {
                cmsURL = cmsURL + "&type=" + type;
            }

            tinyMCE.activeEditor.windowManager.open({
                file : cmsURL,
                title : 'My File Browser',
                width : 420,  
                height : 400,
                resizable : "yes",
                inline : "yes",  
                close_previous : "no"
            }, {
                window : win,
                input : field_name
            });
        }
    
</script>
{% endblock %}

шаблонний фільтр для числівників

Інколи потрібно разом з цифрою вказати слово у вірній множині. Наприклад: 1 коментар, 9 коментарів, 2 коментарі. Django в комплекті має багато шаблонних фільтрів, але для нашої солов'їної треба написати власний.


# -*- coding: utf-8 -*-

from django import template
register = template.Library()

@register.filter
def uapluralize(value, arg="дурак,дураки,дураків"):
    args = arg.split(",")
    number = abs(int(value))
    a = number % 10
    b = number % 100

    if (a == 1) and (b != 11):
        return args[0]
    elif (a >= 2) and (a <= 4) and ((b < 10) or (b >= 20)):
        return args[1]
    else:
        return args[2]
І в шаблоні:
{% load uapluralize %}
{{ m.comments_count|uapluralize:"коментар,коментарі,коментарів" }}

Посилаємо листа через Gmail

Найкращий варіант - юнікод і smtplib
GMAIL_SMTP_SERVER = 'smtp.gmail.com'
GMAIL_SMTP_PORT = 587
GMAIL_USERNAME = 'gmailusername'
GMAIL_PASSWORD = 'gmailpassword'
import smtplib
from email import Charset
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

subject = u'тестовий сабжект'
sender = 'vasyl@gmail.com'
recipient = 'ivan@gmail.com'
from_address = u'Василь '

text = u'Привіт Світ!\nВеселих свят!'

Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
multipart = MIMEMultipart('alternative')
multipart['Subject'] = Header(subject.encode('utf-8'), 'UTF-8').encode()
multipart['To'] = Header(recipient.encode('utf-8'), 'UTF-8').encode()
multipart['From'] = Header(from_address.encode('utf-8'), 'UTF-8').encode()

textpart = MIMEText(text.encode('utf-8'), 'plain', 'UTF-8')
multipart.attach(textpart)

session = smtplib.SMTP(settings.GMAIL_SMTP_SERVER, settings.GMAIL_SMTP_PORT)
session.ehlo()
session.starttls()
session.ehlo()
session.login(settings.GMAIL_USERNAME, settings.GMAIL_PASSWORD)
session.sendmail(sender, recipient, multipart.as_string())
session.quit()