Показ дописів із міткою django. Показати всі дописи
Показ дописів із міткою django. Показати всі дописи

Вибірка Django: Групування об'єктів по днях з їх підрахунком

from django.db import connection
from django.db.models import Count
truncate_date = connection.ops.date_trunc_sql('day', 'created')
qs = NewsContent.objects.extra({'day':truncate_date})
news_report = qs.values('day').annotate(Count('pk')).order_by('day')
і на виході маємо:

[{'pk__count': 110, 'day': u'2015-11-21'}, {'pk__count': 83, 'day': u'2015-11-22'}]

Django grouper і None

Проблемка тут описана.

Рішення:
        {% if news_date.grouper %}
            {% if forloop.parentloop.first and forloop.first %}
                {{ news_date.grouper }}
            {% else %}
                {{ news_date.grouper }}
            {% endif %}
        {% else %}
            Сьогодні
        {% endif %}

IOError: decoder jpeg not available

for x64 OS

pip uninstall PIL
sudo apt-get install libjpeg8-dev
sudo ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /usr/lib
pip install PIL

for x32 OS
pip uninstall PIL
sudo apt-get install libjpeg8-dev
sudo ln -s /usr/lib/i386-linux-gnu/libjpeg.so /usr/lib
pip install PIL
# sudo apt-get install python-dev libjpeg-dev libfreetype6-dev zlib1g-dev

// create these links, if already exists, remove it and re-link it
# ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /usr/lib
# ln -s /usr/lib/x86_64-linux-gnu/libfreetype.so /usr/lib
# ln -s /usr/lib/x86_64-linux-gnu/libz.so /usr/lib

// reinstall PIL
# pip uninstall PIL
# pip install PIL

Кілька версій Django на одній системі

Стикнувся з проблемкою, коли потрібно одночасно розробляти кілька проектів під різні версії Django. Один з варіантів вирішення: felecan.com/2011/definitive-guide-installing-django-ubuntu/

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"),
)

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

Блін, вбив певно годину часу поки нагуглив. Купа неробочого коду в цьому сегменті.
    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

Перша оптимізація 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:"коментар,коментарі,коментарів" }}

loginza + django-avatars + facebook + vkontakte

Авторизуємось через loginza, і якщо facebook нам віддає ім'я і аватар юзернейма, зберігаємо у моделі користувачів.

def loginza_auth_handler(sender, user, identity, **kwargs):
    try:
        #loginza_models.UserMap.objects.get(user=user, verified=True)
        map = loginza_models.UserMap.objects.get(user=user)
        data = json.loads(map.identity.data)

        if data['provider'] == "http://www.facebook.com/":
            system_user = User.objects.get(username=user)
            if data['name']['first_name']:
                system_user.first_name = data['name']['first_name']
            if data['name']['last_name']:
                system_user.last_name = data['name']['last_name']
            if data['photo']:
                try:
                    Avatar.objects.get(user=system_user)
                except:
                    (filename, headers) = urllib.urlretrieve(data['photo'])
                    img_name = '%s.jpg' % os.path.basename(filename)
                    copy_path = os.path.join(settings.MEDIA_ROOT, settings.AVATAR_STORAGE_DIR, system_user.username, img_name)
                    copy_dir = os.path.join(settings.MEDIA_ROOT, settings.AVATAR_STORAGE_DIR, system_user.username)
                    if not os.path.isdir(copy_path):
                        os.makedirs(copy_dir)
                    shutil.copy2(filename, copy_path)
                    path_ava = os.path.join(settings.AVATAR_STORAGE_DIR, system_user.username, img_name)

                    avatar = Avatar(
                        user = system_user,
                        primary = True,
                        avatar = path_ava,
                    )
                    avatar.save()
            system_user.save()

        auth.login(sender, user)

Для Вконтактє ситуація аналогічна, лише Вконтактє віддає малюнок з розширенням, а Фейсбук без.

Django pagination

Вбудована пагінація якась надто скромна і не функціональна. Перепробував кілька реалізацій і зупинився на django-paging. Жахливо документована, але запрацювала на "ура" у всіх аппах.

Зміна xml шаблону Sitemap в Django

Захотілось додати CSS в XML-файл Sitemap'у по типу WordPress'івського. Йдемо в /usr/lib/python2.7/site-packages/django/contrib/sitemaps/templates/ (в Fedora такий шлях) і копіюємо sitemap.xml в свій template каталог проекту. Робимо потрібні зміни в ньому і маємо щастя.

авторизація через соціальні мережі в Django

Дуже класний сервіс, який позбавляє головного болю з САБЖом - loginza.ru
Для Django розроблений апп під нього. Все запрацювало з першого разу. Єдине що поправив в обробнику сигналів, то авторизувати неперевірених, а в мануалі вказано що авторизувати лише перевірених

def loginza_auth_handler(sender, user, identity, **kwargs):
    try:
        # it's enough to have single identity verified to treat user as verified
        #loginza_models.UserMap.objects.get(user=user, verified=True)
        loginza_models.UserMap.objects.get(user=user)
        auth.login(sender, user)
    except loginza_models.UserMap.DoesNotExist:
        sender.session['users_complete_reg_id'] = identity.id
        return redirect(reverse('users.views.complete_registration'))

ThreadedComment і TOP10 найкоментованіших об'єктів

Чомусь розробники не написали такої корисної API. Моє рішення, мабуть тимчасове, бо треба буде потестувати на швидкість.

post_type = ContentType.objects.get_for_model(Post)
counts = ThreadedComment.objects.filter(content_type=post_type).values('object_id').annotate(Count('object_id')).order_by()[:10]
counts = [ ( Post.objects.get(pk=cc['object_id']), cc['object_id__count']) for cc in counts ]

а як прикольно це виглядає у одній стрічці :))

counts = [ ( Post.objects.get(pk=cc['object_id']), cc['object_id__count']) for cc in ThreadedComment.objects.filter(content_type=ContentType.objects.get_for_model(Post)).values('object_id').annotate(Count('object_id')).order_by()[:10] ]


No Django support installed in selected interpreter

Давненько не пітонив. Скачав IDE Pycharm і отримав зразу САБЖ-помилку:



Вирішується додаванням потрібного інтерпретатора:

Сигнали в Django

Простенький приклад. При створенні об'єкту моделі, необхідно в автоматично створити об'єкт в сусідній моделі.

class Task(models.Model):
name = models.CharField(max_length=120)
description = models.TextField()
price = models.CharField(max_length=5)
promotional_code = models.CharField(max_length=24)
city = models.CharField(max_length=24)
is_active = models.BooleanField(default=True)
buyer = models.ForeignKey(User)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.name

class TaskStatus(models.Model):
POSTED, ASSIGNED, COMPLETED, CLOSED = range(1,5)
TASK_STATUS_VALUES = (
(POSTED, 'Posted'),
(ASSIGNED, 'Assigned'),
(COMPLETED, 'Completed'),
(CLOSED, 'Closed'),
)
task = models.ForeignKey(Task)
task_status = models.IntegerField(choices=TASK_STATUS_VALUES, blank=True, null=True)
provider = models.ForeignKey(User, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)

def update_task_status(sender, instance, *args, **kwargs):
TaskStatus.objects.create(task=instance, task_status=1)

models.signals.post_save.connect(update_task_status, sender=Task)