четверг, 28 апреля 2016 г.

Тестирование кэша

Для того, чтобы тестировать кэширование страниц сайта, хорошо подходит django-debug-toolbar
потребуется только установить тулбар и  включить локальный кэш:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    }
}


 Готово! Теперь на вкладке SQL тулбара мы видим только реально осуществленные запросы
а на вкладке Кэш - информацию об обращениях к кэшу

 для кэширвания шаблонов используем конструкции вида:
 где sidebar - название кэша
{% load cache %}
{% cache 500 sidebar sidebar_cache_key %}
    .. sidebar ..
{% endcache %}

или для .jade:
- load cache
- cache 600 sidebar sidebar_cache_key


в видах

    key = myvar_key # ключ кэша
    myvar = cache.get(key)
    if not myvar:
        myvar = ... # get myvar value
        cache.set(key, myvar, 5 * 60)

четверг, 21 апреля 2016 г.

Тестируем время отдачи страницы или выполнения какйо-либо функции

Если нам нужно узнать в тесте время отдачи страницы, то может пригодиться следующий сниппет:

import unittest
import time

from django.conf import settings
from django.test import Client

class SimpleTest(unittest.TestCase):
    def setUp(self):
        self.c = Client()

    def test_CatalogCityTypeThingView(self):
        start = time.time()
        response = self.c.get(
            "/catalog/odejda"
        self.assertEqual(response.status_code, 200)
        print 'response rendering took ', time.time() - start


пятница, 15 апреля 2016 г.

Статистика действий пользователей Django


Смотреть статистику действий пользователей Django поможет следующий код в admin.py:



class LogEntryAdmin(admin.ModelAdmin):
    date_hierarchy = 'action_time'

    readonly_fields = ([f.name for f in LogEntry._meta.fields] +
                       ['object_link', 'action_description', 'user_link'])

    fieldsets = (
        (_('Metadata'), {
            'fields': (
                'action_time',
                'user_link',
                'action_description',
                'object_link',
            )
        }),
        (_('Detail'), {
            'fields': (
                'change_message',
                'content_type',
                'object_id',
                'object_repr',
            )
        }),
    )

    list_filter = [
        UserListFilter,
        'content_type',
        ActionListFilter,
        ('action_time', DateRangeFilter),
    ]

    search_fields = [
        'object_repr',
        'change_message'
    ]

    list_display_links = [
        'action_time',
        'change_message',
    ]
    list_display = [
        'action_time',
        'user_link',
        'content_type',
        'object_link',
        'action_description',
        'change_message',
    ]

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

    def object_link(self, obj):
        if obj.action_flag == DELETION:
            link = escape(obj.object_repr)
        else:
            ct = obj.content_type
            try:
                link = '<a href="%s">%s</a>' % (
                    reverse(
                        'admin:%s_%s_change' % (ct.app_label, ct.model),
                        args=[obj.object_id]
                    ),
                    escape(str(obj.get_edited_object())),
                )
            except NoReverseMatch:
                link = escape(obj.object_repr)
        return link
    object_link.allow_tags = True
    object_link.admin_order_field = 'object_repr'
    object_link.short_description = 'object'

    def user_link(self, obj):
        try:
            ct = ContentType.objects.get_for_model(type(obj.user))
            link = '<a href="%s">%s</a>' % (
                reverse(
                    'admin:%s_%s_change' % (ct.app_label, ct.model),
                    args=[obj.user.pk]
                ),
                escape(force_text(obj.user)),
            )
        except NoReverseMatch:
            link = escape(force_text(obj.user))
        return link
    user_link.allow_tags = True
    user_link.admin_order_field = 'user'
    user_link.short_description = 'user'

    def get_queryset(self, request):
        queryset = super(LogEntryAdmin, self).get_queryset(request)
        return queryset.prefetch_related('content_type')

    def get_actions(self, request):
        actions = super(LogEntryAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

    def action_description(self, obj):
        return action_names[obj.action_flag]
    action_description.short_description = 'Action'


admin.site.register(LogEntry, LogEntryAdmin)

Управление доступом в админке Django

В админке Django есть возможность запретить доступ к страницам редактирования отдельных моделей пользователям кроме superuser.

Это делается в admin.py:

Например:

class LogEntryAdmin(admin.ModelAdmin):

    ...

    # Запрет на добавление всегда
    def has_add_permission(self, request):
        return False

    # Запрет на редактирование если пользователь не superuser
    def has_change_permission(self, request, obj=None):
        return request.user.is_superuser and request.method != 'POST'



    # Запрет на удаление всегда
    def has_delete_permission(self, request, obj=None):
        return False

четверг, 14 апреля 2016 г.

Счетчик кликов по ссылке в Django

Появилась задача для подсчета числа переходов на Django сайте.

Для этого создадим промежуточную страницу /go/<thing_id>/:



Обработчик страницы будет производить две простых вещи:
 - увеличение счетчика числа переходов;
 - перенаправление пользователя на нужный урл;

1. Для начала напишем простенький тест:

       
class ThingCounterTest(unittest.TestCase): 

    def setUp(self):

        self.thing _ = Thing.objects.get_or_create(name="test_channel")



    def tearDown(self):

        self.thing.delete()



    def test_thing_counters(self):

        self.thing.add_click()

        self.assertEqual(self.thing.clicks_today, 1)

        self.assertEqual(self.thing.clicks_yesterday, 0)

        self.assertEqual(self.thing.clicks_last_week, 1)

        self.assertEqual(self.thing.clicks_last_month, 1)

       
 


2. Добавим таблицу в БД для подсчета кликов:



       

class ThingClick(models.Model):

    thing  = models.ForeignKey(

        Thing, related_name='clicks',

        on_delete=models.CASCADE)

    clicked_at = models.DateTimeField(auto_now_add=True)



    class Meta:

        verbose_name = u"Клик на товар"

        verbose_name_plural = u"Клики на товары"



       
 

Не забываем сделать миграцию.

3. Добавим функции для подсчета кликов за последние периоды:

         (models.py)

class Thing(models.Model):

    ...



    def add_click(self):

        self.clicks.create()



    @property

    def clicks_today(self):

        yesterday = timezone.now().date() - timedelta(days=1)

        return self.clicks.filter(

            clicked_at__gt=yesterday).count()



    @property

    def clicks_yesterday(self):

        yesterday = timezone.now().date() - timedelta(days=1)

        return self.clicks.filter(

            clicked_at__gte=yesterday,

            clicked_at__lt=timezone.now().date()).count()



    @property

    def clicks_last_week(self):

        week_ago = timezone.now().date() - timedelta(days=7)

        return self.clicks.filter(clicked_at__gte=week_ago).count()



    @property

    def clicks_last_month(self):

        month_ago = timezone.now().date() - timedelta(days=30)

        return self.clicks.filter(clicked_at__gte=month_ago).count()

       
 



4. напишем вид, который будет считать ссылки и редиректить на нужную страницу (ссылка на которую у меня уже хранится в thing.link):
 (views.py)
        
def thing_click_admitad(request, thing_id=None):

    """

        Переход по ссылке thing.link со счетчиком кликов

        по каналу

    """

    if thing_id:

        thing = get_object_or_404(

            Thing.objects.select_related("thing_set__channel"),

            id=thing_id)

        try:

            thing.add_click()

        except Exception, e:

            logger.error("Failed to add a click for thing: " + str(e))

        if thing.link:

            return HttpResponseRedirect(thing.link)

        else:

            raise Http404


5. Добавим в urls.py соответствующий шаблон ссылок:


       

    url(r"^go/(?P<thing_id>[0-9]+)/$",
        "statistic.views.thing_click",
        name="counted_link"),



6. Наконец, добавим в админку отображение результатов нашей статистики:


       
class ThingAdmin(admin.ModelAdmin):
    list_display = (
        "name", "url", "clicks_today", "clicks_yesterday",
        "clicks_last_week", "clicks_last_month")

    def clicks_today(self, obj):
        return obj.clicks_today
    clicks_today.short_description = u'Кликов сегодня'

    def clicks_yesterday(self, obj):
        return obj.clicks_yesterday
    clicks_yesterday.short_description = u'Кликов вчера'

    def clicks_last_week(self, obj):
        return obj.clicks_last_week
    clicks_last_week.short_description = u'Кликов за последнюю неделю'

    def clicks_last_month(self, obj):
        return obj.clicks_last_month
    clicks_last_month.short_description = u'Кликов за последний месяц'