• Home
  • Учебник по ExtJS
  • О сайте
  •  


    Удобный способ уведомления об ошибках

    Сентябрь 11th, 2008

    Как получать сообщения по электронной почте, когда в приложении возникает исключение?

    Ниже приведен пример кода, который позволяет реализовать такую функцию, причем ведет учет сообщений по ранее произошедшим событиям, не позволяя заваливать бесполезным мусором ящик разработчика:

    import sys, logging, traceback from google.appengine.ext import webapp from google.appengine.api import memcache class BaseRequestHandler(webapp.RequestHandler):     def handle_exception(self, exception, debug_mode):         exception_name = sys.exc_info()[0].__name__         exception_details = str(sys.exc_info()[1])         exception_traceback = ''.join(traceback.format_exception(*sys.exc_info()))         logging.error(exception_traceback)         exception_expiration = 3600 # в секундах (задаем максимум 1 сообщение в час по исключению)         mail_admin = "yourmail@yourdomain" # указывается администратор приложения         sitename = "yourapplication"         throttle_name = u'исключение-'+exception_name         throttle = memcache.get(throttle_name)         if throttle is None:             memcache.add(throttle_name, 1, exception_expiration)             subject = u'[%s] исключение [%s: %s]' % (sitename, exception_name, exception_details)             mail.send_mail_to_admins(sender=mail_admin,                                      subject=subject,                                      body=exception_traceback)         template_values = {}         if users.is_current_user_admin():             template_values['traceback'] = exception_traceback         self.response.out.write(template.render('error.html',                                                 template_values)) class MyHandler(BaseRequestHandler):     def get(self):         ...



    Автоматизированное тестирование приложений Google App Engine

    Август 28th, 2008

    В этой статье мы рассмотрим пример добавления среды тестирования к учебному приложению Гостевая книга, которое описано в Руководстве для начинающих. Вы можете загрузить все описываемые тесты по этой ссылке.

    Начальная настройка

    Python уже имеет в своей стандартной поставке модуль unittest, поэтому беспокоиться о его установке не нужно. К сожалению, существуют несколько специфических для App Engine интерфейсов (такие как datastore и users), которые должны быть либо правильно настроены, либо вообще убраны из тестов. Я решил использовать фреймворк mocker и поставить заглушки на специфический код от Google, вместо того, чтобы заниматься созданием самопального хранилища, объектов пользователей и тому подобного.

    Read the rest of this entry »




    Операции AVG и SUM в Google App Engine

    Август 14th, 2008

    Разработчики, которые ранее использовали обычные реляционные базы данных часто сразу не могут переложить свои навыки на хранилище App Engine и заваливают буквально шквалом вопросов. Один из них: Как выполнить операции SUM и AVG с данными хранилища?. Вы не можете напрямую выполнять в хранилище подобные запросы, вместо этого необходимо постоянно вести учет итогов и хранить их в специальном счетчике. Вот пример такой реализации:

    Файл models.py:

    from appengine_django.models import BaseModel
    from google.appengine.ext import db
    from google.appengine.ext.db import NotSavedError
    from decimal import Decimal, getcontext
    
    class Callable:
     # это класс выполняет обертывание метода, так что мы можем вызвать его напрямую без создания экземпляра.
    
     def __init__(self, anycallable):
      self.__call__ = anycallable
    
    class GlobalCounter(BaseModel):
     name = db.StringProperty(required=True)
     value = db.IntegerProperty(required=True)
    
    class Rating(BaseModel):
     user = db.UserProperty(required=True)
     rating = db.IntegerProperty(required=True) # скрываем его
     oldRating = 0
    
     def set_rating(self, val):
      self.oldRating = self.rating
      self.rating = val
    
     def put(self):
      self.save()
    
     def save(self):
    
      # получаем значения счетчиков из хранилища
      votes = GlobalCounter.all().filter("name = ", "numberOfVotes").get()
      total = GlobalCounter.all().filter("name = ", "totalAmountOfVotes").get()
    
      if (votes == None):
       votes = GlobalCounter(name = "numberOfVotes", value = 0)
       votes.save()
    
      if (total == None):
       total  = GlobalCounter(name = "totalAmountOfVotes", value = 0)
       votes.save()
    
      # проверяем, является ли рейтинг новым или пользователь просто меняет свой выбор
      isSaved = False
      try:
       self.key()
       isSaved = True
      except NotSavedError:
       isSaved = False
    
      if (isSaved):
       # если пользователь пересохраняет свой выбор (возможно меняя его) мы вычитаем из итогов старое значение и добавляем новое
       total.value = total.value - self.oldRating + self.rating
      else:
       votes.value += 1
       total.value += self.rating
    
      votes.save()
      total.save()
    
      BaseModel.save(self)
    
     def ___getAverage___():
      votesCounter = GlobalCounter.all().filter("name = ", "numberOfVotes").get()
      totalCounter = GlobalCounter.all().filter("name = ", "totalAmountOfVotes").get()
      getcontext().prec = 3
      return Decimal(str(totalCounter.value)) / Decimal(str(votesCounter.value))
    
     average = Callable(___getAverage___) # average() будет теперь вести себя как статическая переменная
    
     def ___getTotal___():
      totalCounter = GlobalCounter.all().filter("name = ", "totalAmountOfVotes").get()
      return totalCounter.value
    
     sum = Callable(___getTotal___) # sum() будет теперь вести себя как статическая переменная

    А использовать это можно так… Файл tests.py:

    import os
    from decimal import Decimal, getcontext
    
    #импортируем загрушки
    from google.appengine.api import apiproxy_stub_map
    from google.appengine.api import datastore_file_stub
    from google.appengine.api import mail_stub
    from google.appengine.api import urlfetch_stub
    from google.appengine.api import user_service_stub
    
    from google.appengine.api import users
    
    # импортируем замену Django Forms из SDK App Enginee
    from google.appengine.ext.db.djangoforms import ModelForm
    
    # Я использую библиотеку Python Mocker для создания тестовых объектов: http://labix.org/mocker
    import mocker
    from mocker import MockerTestCase
    
    # импортируем нашу модель
    from avgtest.models import Rating
    from avgtest.models import GlobalCounter
    
    class TestStart(MockerTestCase):
    
     def setUp(self):
    
      apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()
    
      # Используем мнимое хранилище.
      # В этой точке приложения задается, что все обращения к хранилищу, например операции get и put,
      # будут выполняться с временными данными, расположенными в памяти.
    
      stub = datastore_file_stub.DatastoreFileStub(u'myTemporaryDataStorage', '/dev/null', '/dev/null')
      apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', stub)
    
      # Используем заглушку для UserService.
      apiproxy_stub_map.apiproxy.RegisterStub('user', user_service_stub.UserServiceStub())
      os.environ['AUTH_DOMAIN'] = 'gmail.com'
      os.environ['USER_EMAIL'] = 'myself@appengineguy.com' # set to '' for no logged in user
      os.environ['SERVER_NAME'] = 'fakeserver.com'
      os.environ['SERVER_PORT'] = '9999'
    
      # Используем заглушку для urlfetch.
      apiproxy_stub_map.apiproxy.RegisterStub('urlfetch', urlfetch_stub.URLFetchServiceStub())
    
      # Используем заглушку для почты.
      apiproxy_stub_map.apiproxy.RegisterStub('mail', mail_stub.MailServiceStub())
    
      self.HttpResponseRedirect = self.mocker.replace("django.http.HttpResponseRedirect")
    
      self.render_to_response = self.mocker.replace("django.shortcuts.render_to_response")
    
     def testAverage(self):
      rA = Rating(user = users.get_current_user(), rating = 1)
      rA.save()
      self.assertEquals(1, Rating.average())
      rB = Rating(user = users.get_current_user(), rating = 4)
      rB.put()
      self.assertEquals(Decimal("2.5"), Rating.average())
      rC = Rating(user = users.get_current_user(), rating = 2)
      rC.save()
      self.assertEquals(Decimal("2.33"), Rating.average())
    
     def testSum(self):
      rA = Rating(user = users.get_current_user(), rating = 2)
      rA.save()
      rB = Rating(user = users.get_current_user(), rating = 4)
      rB.put()
      rC = Rating(user = users.get_current_user(), rating = 3)
      rC.save()
      self.assertEquals(9, Rating.sum())
    
     def testAverageChangeVote(self):
      rA = Rating(user = users.get_current_user(), rating = 1)
      rA.save()
      self.assertEquals(1, Rating.average())
      rB = Rating(user = users.get_current_user(), rating = 4)
      rB.put()
      self.assertEquals(Decimal("2.5"), Rating.average())
      rC = Rating(user = users.get_current_user(), rating = 2)
      rC.save()
      self.assertEquals(Decimal("2.33"), Rating.average())
      rC.set_rating(3) # пользователь C изменил свою оценку на 3
      rC.save()
      self.assertEquals(Decimal("2.67"), Rating.average())

    Замечание по производительности: Приведенный выше пример показывает неэффективную работу с счетчиками, однако он указан только для объяснения принципов работы. На практике же необходимо использовать разделение счетчиков.