<commit_before># -*- coding: utf-8 -*-
# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
from base64 import b64encode
import os.path

from django.contrib.syndication.views import Feed
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404
from django.utils.feedgenerator import Atom1Feed
from django.conf import settings
from django.http import Http404
from django.contrib.sites.models import Site

from basicauth import logged_in_or_basicauth, factory_decorator
from catalogue.models import Book, Tag
from catalogue.views import books_starting_with

_root_feeds = (
    {
        u"category": u"",
        u"link": u"opds_user",
        u"link_args": [],
        u"title": u"Moje półki",
        u"description": u"Półki użytkownika dostępne po zalogowaniu"
    },
    {
        u"category": u"author",
        u"link": u"opds_by_category",
        u"link_args": [u"author"],
        u"title": u"Autorzy",
        u"description": u"Utwory wg autorów"
    },
    {
        u"category": u"kind",
        u"link": u"opds_by_category",
        u"link_args": [u"kind"],
        u"title": u"Rodzaje",
        u"description": u"Utwory wg rodzajów"
    },
    {
        u"category": u"genre",
        u"link": u"opds_by_category",
        u"link_args": [u"genre"],
        u"title": u"Gatunki",
        u"description": u"Utwory wg gatunków"
    },
    {
        u"category": u"epoch",
        u"link": u"opds_by_category",
        u"link_args": [u"epoch"],
        u"title": u"Epoki",
        u"description": u"Utwory wg epok"
    },
)

def full_url(url):
    return "http://%s%s" % (Site.objects.get_current().domain, url)

class OPDSFeed(Atom1Feed):
    link_rel = u"subsection"
    link_type = u"application/atom+xml"

    _book_parent_img = full_url(os.path.join(settings.STATIC_URL, "img/book-parent.png"))
    try:
        _book_parent_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book-parent.png")))
    except:
        _book_parent_img_size = ''

    _book_img = full_url(os.path.join(settings.STATIC_URL, "img/book.png"))
    try:
        _book_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book.png")))
    except:
        _book_img_size = ''

    def add_root_elements(self, handler):
        super(OPDSFeed, self).add_root_elements(handler)
        handler.addQuickElement(u"link", None,
                                {u"href": reverse("opds_authors"),
                                 u"rel": u"start",
                                 u"type": u"application/atom+xml"})
        handler.addQuickElement(u"link", None,
                                {u"href": full_url(os.path.join(settings.STATIC_URL, "opensearch.xml")),
                                 u"rel": u"search",
                                 u"type": u"application/opensearchdescription+xml"})

    def add_item_elements(self, handler, item):
        """ modified from Atom1Feed.add_item_elements """
        handler.addQuickElement(u"title", item['title'])

        # add a OPDS Navigation link if there's no enclosure
        if item['enclosure'] is None:
            handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"subsection", u"type": u"application/atom+xml"})
            # add a "green book" icon
            handler.addQuickElement(u"link", '',
                {u"rel": u"http://opds-spec.org/thumbnail",
                 u"href": self._book_parent_img,
                 u"length": self._book_parent_img_size,
                 u"type": u"image/png"})
        if item['pubdate'] is not None:
            handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))

        # Author information.
        if item['author_name'] is not None:
            handler.startElement(u"author", {})
            handler.addQuickElement(u"name", item['author_name'])
            if item['author_email'] is not None:
                handler.addQuickElement(u"email", item['author_email'])
            if item['author_link'] is not None:
                handler.addQuickElement(u"uri", item['author_link'])
            handler.endElement(u"author")

        # Unique ID.
        if item['unique_id'] is not None:
            unique_id = item['unique_id']
        else:
            unique_id = get_tag_uri(item['link'], item['pubdate'])
        handler.addQuickElement(u"id", unique_id)

        # Summary.
        # OPDS needs type=text
        if item['description'] is not None:
            handler.addQuickElement(u"summary", item['description'], {u"type": u"text"})

        # Enclosure as OPDS Acquisition Link
        if item['enclosure'] is not None:
            handler.addQuickElement(u"link", '',
                {u"rel": u"http://opds-spec.org/acquisition",
                 u"href": item['enclosure'].url,
                 u"length": item['enclosure'].length,
                 u"type": item['enclosure'].mime_type})
            # add a "red book" icon
            handler.addQuickElement(u"link", '',
                {u"rel": u"http://opds-spec.org/thumbnail",
                 u"href": self._book_img,
                 u"length": self._book_img_size,
                 u"type": u"image/png"})

        # Categories.
        for cat in item['categories']:
            handler.addQuickElement(u"category", u"", {u"term": cat})

        # Rights.
        if item['item_copyright'] is not None:
            handler.addQuickElement(u"rights", item['item_copyright'])

class AcquisitionFeed(Feed):
    feed_type = OPDSFeed
    link = u'http://www.wolnelektury.pl/'
    item_enclosure_mime_type = "application/epub+zip"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def item_title(self, book):
        return book.title

    def item_description(self):
        return u''

    def item_link(self, book):
        return book.get_absolute_url()

    def item_author_name(self, book):
        try:
            return book.tags.filter(category='author')[0].name
        except KeyError:
            return u''

    def item_author_link(self, book):
        try:
            return book.tags.filter(category='author')[0].get_absolute_url()
        except KeyError:
            return u''

    def item_enclosure_url(self, book):
        return full_url(book.root_ancestor.epub_file.url)

    def item_enclosure_length(self, book):
        return book.root_ancestor.epub_file.size

class RootFeed(Feed):
    feed_type = OPDSFeed
    title = u'Wolne Lektury'
    link = u'http://www.wolnelektury.pl/'
    description = u"Spis utworów na stronie http://WolneLektury.pl"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def items(self):
        return _root_feeds

    def item_title(self, item):
        return item['title']

    def item_link(self, item):
        return reverse(item['link'], args=item['link_args'])

    def item_description(self, item):
        return item['description']

class ByCategoryFeed(Feed):
    feed_type = OPDSFeed
    link = u'http://www.wolnelektury.pl/'
    description = u"Spis utworów na stronie http://WolneLektury.pl"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def get_object(self, request, category):
        feed = [feed for feed in _root_feeds if feed['category']==category]
        if feed:
            feed = feed[0]
        else:
            raise Http404

        return feed

    def title(self, feed):
        return feed['title']

    def items(self, feed):
        return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0)

    def item_title(self, item):
        return item.name

    def item_link(self, item):
        return reverse("opds_by_tag", args=[item.category, item.slug])

    def item_description(self):
        return u''

class ByTagFeed(AcquisitionFeed):
    def link(self, tag):
        return tag.get_absolute_url()

    def title(self, tag):
        return tag.name

    def description(self, tag):
        return u"Spis utworów na stronie http://WolneLektury.pl"

    def get_object(self, request, category, slug):
        return get_object_or_404(Tag, category=category, slug=slug)

    def items(self, tag):
        books = Book.tagged.with_any([tag])
        l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
        descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
        if descendants_keys:
            books = books.exclude(pk__in=descendants_keys)

        return books

@factory_decorator(logged_in_or_basicauth())
class UserFeed(Feed):
    feed_type = OPDSFeed
    link = u'http://www.wolnelektury.pl/'
    description = u"Półki użytkownika na stronie http://WolneLektury.pl"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def get_object(self, request):
        return request.user

    def title(self, user):
        return u"Półki użytkownika %s" % user.username

    def items(self, user):
        return (tag for tag in Tag.objects.filter(category='set', user=user) if tag.get_count() > 0)

    def item_title(self, item):
        return item.name

    def item_link(self, item):
        return reverse("opds_user_set", args=[item.slug])

    def item_description(self):
        return u''

@factory_decorator(logged_in_or_basicauth())
class UserSetFeed(AcquisitionFeed):
    def link(self, tag):
        return tag.get_absolute_url()

    def title(self, tag):
        return tag.name

    def description(self, tag):
        return u"Spis utworów na stronie http://WolneLektury.pl"

    def get_object(self, request, slug):
        return get_object_or_404(Tag, category='set', slug=slug, user=request.user)

    def items(self, tag):
        return Book.tagged.with_any([tag])

class SearchFeed(AcquisitionFeed):
    description = u"Wyniki wyszukiwania na stronie WolneLektury.pl"
    title = u"Wyniki wyszukiwania"

    def get_object(self, request):
        return request.GET.get('q', '')

    def get_link(self, query):
        return "%s?q=%s" % (reverse('search'), query)

    def items(self, query):
        try:
            return books_starting_with(query)
        except ValueError:
            # too short a query
            return []
<commit_msg>removed class decorator syntax for python2.5 compability<commit_after># -*- coding: utf-8 -*-
# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
from base64 import b64encode
import os.path

from django.contrib.syndication.views import Feed
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404
from django.utils.feedgenerator import Atom1Feed
from django.conf import settings
from django.http import Http404
from django.contrib.sites.models import Site

from basicauth import logged_in_or_basicauth, factory_decorator
from catalogue.models import Book, Tag
from catalogue.views import books_starting_with

_root_feeds = (
    {
        u"category": u"",
        u"link": u"opds_user",
        u"link_args": [],
        u"title": u"Moje półki",
        u"description": u"Półki użytkownika dostępne po zalogowaniu"
    },
    {
        u"category": u"author",
        u"link": u"opds_by_category",
        u"link_args": [u"author"],
        u"title": u"Autorzy",
        u"description": u"Utwory wg autorów"
    },
    {
        u"category": u"kind",
        u"link": u"opds_by_category",
        u"link_args": [u"kind"],
        u"title": u"Rodzaje",
        u"description": u"Utwory wg rodzajów"
    },
    {
        u"category": u"genre",
        u"link": u"opds_by_category",
        u"link_args": [u"genre"],
        u"title": u"Gatunki",
        u"description": u"Utwory wg gatunków"
    },
    {
        u"category": u"epoch",
        u"link": u"opds_by_category",
        u"link_args": [u"epoch"],
        u"title": u"Epoki",
        u"description": u"Utwory wg epok"
    },
)

def full_url(url):
    return "http://%s%s" % (Site.objects.get_current().domain, url)

class OPDSFeed(Atom1Feed):
    link_rel = u"subsection"
    link_type = u"application/atom+xml"

    _book_parent_img = full_url(os.path.join(settings.STATIC_URL, "img/book-parent.png"))
    try:
        _book_parent_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book-parent.png")))
    except:
        _book_parent_img_size = ''

    _book_img = full_url(os.path.join(settings.STATIC_URL, "img/book.png"))
    try:
        _book_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book.png")))
    except:
        _book_img_size = ''

    def add_root_elements(self, handler):
        super(OPDSFeed, self).add_root_elements(handler)
        handler.addQuickElement(u"link", None,
                                {u"href": reverse("opds_authors"),
                                 u"rel": u"start",
                                 u"type": u"application/atom+xml"})
        handler.addQuickElement(u"link", None,
                                {u"href": full_url(os.path.join(settings.STATIC_URL, "opensearch.xml")),
                                 u"rel": u"search",
                                 u"type": u"application/opensearchdescription+xml"})

    def add_item_elements(self, handler, item):
        """ modified from Atom1Feed.add_item_elements """
        handler.addQuickElement(u"title", item['title'])

        # add a OPDS Navigation link if there's no enclosure
        if item['enclosure'] is None:
            handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"subsection", u"type": u"application/atom+xml"})
            # add a "green book" icon
            handler.addQuickElement(u"link", '',
                {u"rel": u"http://opds-spec.org/thumbnail",
                 u"href": self._book_parent_img,
                 u"length": self._book_parent_img_size,
                 u"type": u"image/png"})
        if item['pubdate'] is not None:
            handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))

        # Author information.
        if item['author_name'] is not None:
            handler.startElement(u"author", {})
            handler.addQuickElement(u"name", item['author_name'])
            if item['author_email'] is not None:
                handler.addQuickElement(u"email", item['author_email'])
            if item['author_link'] is not None:
                handler.addQuickElement(u"uri", item['author_link'])
            handler.endElement(u"author")

        # Unique ID.
        if item['unique_id'] is not None:
            unique_id = item['unique_id']
        else:
            unique_id = get_tag_uri(item['link'], item['pubdate'])
        handler.addQuickElement(u"id", unique_id)

        # Summary.
        # OPDS needs type=text
        if item['description'] is not None:
            handler.addQuickElement(u"summary", item['description'], {u"type": u"text"})

        # Enclosure as OPDS Acquisition Link
        if item['enclosure'] is not None:
            handler.addQuickElement(u"link", '',
                {u"rel": u"http://opds-spec.org/acquisition",
                 u"href": item['enclosure'].url,
                 u"length": item['enclosure'].length,
                 u"type": item['enclosure'].mime_type})
            # add a "red book" icon
            handler.addQuickElement(u"link", '',
                {u"rel": u"http://opds-spec.org/thumbnail",
                 u"href": self._book_img,
                 u"length": self._book_img_size,
                 u"type": u"image/png"})

        # Categories.
        for cat in item['categories']:
            handler.addQuickElement(u"category", u"", {u"term": cat})

        # Rights.
        if item['item_copyright'] is not None:
            handler.addQuickElement(u"rights", item['item_copyright'])

class AcquisitionFeed(Feed):
    feed_type = OPDSFeed
    link = u'http://www.wolnelektury.pl/'
    item_enclosure_mime_type = "application/epub+zip"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def item_title(self, book):
        return book.title

    def item_description(self):
        return u''

    def item_link(self, book):
        return book.get_absolute_url()

    def item_author_name(self, book):
        try:
            return book.tags.filter(category='author')[0].name
        except KeyError:
            return u''

    def item_author_link(self, book):
        try:
            return book.tags.filter(category='author')[0].get_absolute_url()
        except KeyError:
            return u''

    def item_enclosure_url(self, book):
        return full_url(book.root_ancestor.epub_file.url)

    def item_enclosure_length(self, book):
        return book.root_ancestor.epub_file.size

class RootFeed(Feed):
    feed_type = OPDSFeed
    title = u'Wolne Lektury'
    link = u'http://www.wolnelektury.pl/'
    description = u"Spis utworów na stronie http://WolneLektury.pl"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def items(self):
        return _root_feeds

    def item_title(self, item):
        return item['title']

    def item_link(self, item):
        return reverse(item['link'], args=item['link_args'])

    def item_description(self, item):
        return item['description']

class ByCategoryFeed(Feed):
    feed_type = OPDSFeed
    link = u'http://www.wolnelektury.pl/'
    description = u"Spis utworów na stronie http://WolneLektury.pl"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def get_object(self, request, category):
        feed = [feed for feed in _root_feeds if feed['category']==category]
        if feed:
            feed = feed[0]
        else:
            raise Http404

        return feed

    def title(self, feed):
        return feed['title']

    def items(self, feed):
        return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0)

    def item_title(self, item):
        return item.name

    def item_link(self, item):
        return reverse("opds_by_tag", args=[item.category, item.slug])

    def item_description(self):
        return u''

class ByTagFeed(AcquisitionFeed):
    def link(self, tag):
        return tag.get_absolute_url()

    def title(self, tag):
        return tag.name

    def description(self, tag):
        return u"Spis utworów na stronie http://WolneLektury.pl"

    def get_object(self, request, category, slug):
        return get_object_or_404(Tag, category=category, slug=slug)

    def items(self, tag):
        books = Book.tagged.with_any([tag])
        l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
        descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
        if descendants_keys:
            books = books.exclude(pk__in=descendants_keys)

        return books

#@factory_decorator(logged_in_or_basicauth())
class UserFeed(Feed):
    feed_type = OPDSFeed
    link = u'http://www.wolnelektury.pl/'
    description = u"Półki użytkownika na stronie http://WolneLektury.pl"
    author_name = u"Wolne Lektury"
    author_link = u"http://www.wolnelektury.pl/"

    def get_object(self, request):
        return request.user

    def title(self, user):
        return u"Półki użytkownika %s" % user.username

    def items(self, user):
        return (tag for tag in Tag.objects.filter(category='set', user=user) if tag.get_count() > 0)

    def item_title(self, item):
        return item.name

    def item_link(self, item):
        return reverse("opds_user_set", args=[item.slug])

    def item_description(self):
        return u''

# no class decorators in python 2.5
UserFeed = factory_decorator(logged_in_or_basicauth())(UserFeed)

#@factory_decorator(logged_in_or_basicauth())
class UserSetFeed(AcquisitionFeed):
    def link(self, tag):
        return tag.get_absolute_url()

    def title(self, tag):
        return tag.name

    def description(self, tag):
        return u"Spis utworów na stronie http://WolneLektury.pl"

    def get_object(self, request, slug):
        return get_object_or_404(Tag, category='set', slug=slug, user=request.user)

    def items(self, tag):
        return Book.tagged.with_any([tag])

# no class decorators in python 2.5
UserSetFeed = factory_decorator(logged_in_or_basicauth())(UserSetFeed)

class SearchFeed(AcquisitionFeed):
    description = u"Wyniki wyszukiwania na stronie WolneLektury.pl"
    title = u"Wyniki wyszukiwania"

    def get_object(self, request):
        return request.GET.get('q', '')

    def get_link(self, query):
        return "%s?q=%s" % (reverse('search'), query)

    def items(self, query):
        try:
            return books_starting_with(query)
        except ValueError:
            # too short a query
            return []
<|endoftext|>


<commit_before># Prevent logging of Elasticsearch queries
import logging
logging.disable(logging.CRITICAL)

import pytest

from django.core.urlresolvers import reverse
from unittest.mock import patch

from accounts.tests.test_models import create_new_user
from functional_tests.base import FunctionalTest
from sample.tests.test_views import route_home as sample_route_home
from samplemulti.tests.test_views import route_home as samplemulti_route_home
from search.tests.test_index import create_temp_indices

LIST_EMPTY_RESULTS_TEXT = 'No results found.'

@pytest.mark.usefixtures('es')
class SearchTest(FunctionalTest):

    fixtures = [
        'sample_global_key_values',
        'sample',
        'samplemulti',
        'wocat',
        'sample_questionnaires_search',
    ]

    def setUp(self):
        super(SearchTest, self).setUp()
        create_temp_indices([('sample', '2015'), ('samplemulti', '2015')])

    @patch('questionnaire.views.get_configuration_index_filter')
    def test_search_home(self, mock_get_configuration_index_filter):

        mock_get_configuration_index_filter.return_value = [
            'sample', 'samplemulti']

        # Alice goes to the landing page and sees the search field
        self.browser.get(self.live_server_url + reverse('home'))

        # She enters a search value and submits the search form
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.apply_filter()

        # She sees that she has been taken to the WOCAT configuration
        # where the search results are listed
        self.assertIn('/wocat/list', self.browser.current_url)
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 3)

        # She sees that this is the same result as when searching in
        # WOCAT (at the top of the page)
        self.findBy('xpath', '//input[@type="search"]').clear()
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.apply_filter()

        self.assertIn('/wocat/list', self.browser.current_url)
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 3)

    def test_search_sample(self):

        # Alice goes to the home page of the sample configuration and
        # sees the search field at the top of the page.
        self.browser.get(self.live_server_url + reverse(sample_route_home))

        # She enters a search value and submits the search form
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.set_input_value('search-type', 'sample')
        self.apply_filter()

        # She sees that she has been taken to the SAMPLE configuration
        # where the search results are listed
        self.assertIn('/sample/list', self.browser.current_url)
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 2)

        # Vice versa, the samplemulti configuration only shows results
        # from the samplemulti configuration
        self.browser.get(
            self.live_server_url + reverse(samplemulti_route_home))
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.set_input_value('search-type', 'samplemulti')
        self.apply_filter()
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 1)

@pytest.mark.usefixtures('es')
class SearchTestAdmin(FunctionalTest):

    fixtures = [
        'sample_global_key_values',
        'sample',
        'sample_questionnaires',
    ]

    def setUp(self):
        super(SearchTestAdmin, self).setUp()
        user = create_new_user()
        user.is_superuser = True
        user.save()
        self.user = user

    def test_search_admin(self):

        # Alice logs in
        self.doLogin(user=self.user)

        # She sees the button to access the search administration in the
        # top navigation bar and clicks it.
        self.clickUserMenu(self.user)
        navbar = self.findBy('class_name', 'top-bar')
        navbar.find_element_by_link_text('Search Index Administration').click()

        # She sees that the active configurations are listed, all of
        # them having no index
        self.findBy(
            'xpath',
            '//tbody/tr[1]/td[text()="sample 2015"]')
        self.assertEqual(
            self.findBy(
                'xpath',
                '//tbody/tr[1]/td/span/strong[@class="text-warning"]').text,
            'No Index!')

        # She creates the index for the sample configuration
        self.findBy('xpath', '//a[contains(@href, "/index/sample/")]').click()

        # She sees the index was created successfully
        self.findBy('xpath', '//div[contains(@class, "success")]')
        self.assertEqual(
            self.findBy(
                'xpath',
                '//tbody/tr[1]/td/span/strong[@class="text-warning"]').text,
            '0 / 2')

        # She updates the newly created index
        self.findBy('xpath', '//a[contains(@href, "/update/sample/")]').click()

        # She sees that the index was updated successfully
        self.findBy('xpath', '//div[contains(@class, "success")]')
        self.findByNot(
            'xpath', '//tbody/tr[1]/td/span/strong[@class="text-warning"]')
        self.assertEqual(
            self.findBy(
                'xpath',
                '//tbody/tr[1]/td/span/strong[@class="text-ok"]').text,
            '2 / 2')

        # She decides to delete all indices
        self.findBy('xpath', '//a[@href="/en/search/delete/"]').click()

        # She sees that all indices were deleted
        self.findBy('xpath', '//div[contains(@class, "success")]')
        self.findBy(
            'xpath',
            '//tbody/tr[1]/td[text()="sample 2015"]')
        self.assertEqual(
            self.findBy(
                'xpath',
                '//tbody/tr[1]/td/span/strong[@class="text-warning"]').text,
            'No Index!')
<commit_msg>Do not delete ES indices during testing<commit_after># Prevent logging of Elasticsearch queries
import logging
logging.disable(logging.CRITICAL)

import pytest

from django.core.urlresolvers import reverse
from unittest.mock import patch

from accounts.tests.test_models import create_new_user
from functional_tests.base import FunctionalTest
from sample.tests.test_views import route_home as sample_route_home
from samplemulti.tests.test_views import route_home as samplemulti_route_home
from search.tests.test_index import create_temp_indices

LIST_EMPTY_RESULTS_TEXT = 'No results found.'

@pytest.mark.usefixtures('es')
class SearchTest(FunctionalTest):

    fixtures = [
        'sample_global_key_values',
        'sample',
        'samplemulti',
        'wocat',
        'sample_questionnaires_search',
    ]

    def setUp(self):
        super(SearchTest, self).setUp()
        create_temp_indices([
            ('sample', '2015'),
            ('samplemulti', '2015'),
        ])

    @patch('questionnaire.views.get_configuration_index_filter')
    def test_search_home(self, mock_get_configuration_index_filter):

        mock_get_configuration_index_filter.return_value = [
            'sample', 'samplemulti']

        # Alice goes to the landing page and sees the search field
        self.browser.get(self.live_server_url + reverse('home'))

        # She enters a search value and submits the search form
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.apply_filter()

        # She sees that she has been taken to the WOCAT configuration
        # where the search results are listed
        self.assertIn('/wocat/list', self.browser.current_url)
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 3)

        # She sees that this is the same result as when searching in
        # WOCAT (at the top of the page)
        self.findBy('xpath', '//input[@type="search"]').clear()
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.apply_filter()

        self.assertIn('/wocat/list', self.browser.current_url)
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 3)

    def test_search_sample(self):

        # Alice goes to the home page of the sample configuration and
        # sees the search field at the top of the page.
        self.browser.get(self.live_server_url + reverse(sample_route_home))

        # She enters a search value and submits the search form
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.set_input_value('search-type', 'sample')
        self.apply_filter()

        # She sees that she has been taken to the SAMPLE configuration
        # where the search results are listed
        self.assertIn('/sample/list', self.browser.current_url)
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 2)

        # Vice versa, the samplemulti configuration only shows results
        # from the samplemulti configuration
        self.browser.get(
            self.live_server_url + reverse(samplemulti_route_home))
        self.findBy('xpath', '//input[@type="search"]').send_keys('key')
        self.set_input_value('search-type', 'samplemulti')
        self.apply_filter()
        results = self.findManyBy(
            'xpath', '//article[contains(@class, "tech-item")]')
        self.assertEqual(len(results), 1)

@pytest.mark.usefixtures('es')
class SearchTestAdmin(FunctionalTest):

    fixtures = [
        'sample_global_key_values',
        'sample',
        'sample_questionnaires',
    ]

    def setUp(self):
        super(SearchTestAdmin, self).setUp()
        create_temp_indices([
            ('sample', '2015')
        ])
        user = create_new_user()
        user.is_superuser = True
        user.save()
        self.user = user

    def test_search_admin(self):

        # Alice logs in
        self.doLogin(user=self.user)

        # She sees the button to access the search administration in the
        # top navigation bar and clicks it.
        self.clickUserMenu(self.user)
        navbar = self.findBy('class_name', 'top-bar')
        navbar.find_element_by_link_text('Search Index Administration').click()

        # First, delete the sample index.
        self.findBy('xpath', '//a[contains(@href, "/delete/sample/")]').click()

        # She sees that the active configurations are listed, all of
        # them having no index
        self.findBy(
            'xpath',
            '//tbody/tr[1]/td[text()="sample 2015"]')
        self.assertEqual(
            self.findBy(
                'xpath',
                '//tbody/tr[1]/td/span/strong[@class="text-warning"]').text,
            'No Index!')

        # She creates the index for the sample configuration
        self.findBy('xpath', '//a[contains(@href, "/index/sample/")]').click()

        # She sees the index was created successfully
        self.findBy('xpath', '//div[contains(@class, "success")]')
        self.assertEqual(
            self.findBy(
                'xpath',
                '//tbody/tr[1]/td/span/strong[@class="text-warning"]').text,
            '0 / 2')

        # She updates the newly created index
        self.findBy('xpath', '//a[contains(@href, "/update/sample/")]').click()

        # She sees that the index was updated successfully
        self.findBy('xpath', '//div[contains(@class, "success")]')
        self.findByNot(
            'xpath', '//tbody/tr[1]/td/span/strong[@class="text-warning"]')
        self.assertEqual(
            self.findBy(
                'xpath',
                '//tbody/tr[1]/td/span/strong[@class="text-ok"]').text,
            '2 / 2')

        # Do not delete all indices, as this will also delete the currently
        # active ones.
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import re

from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_split_str
from odoo.tools.misc import mod10r

l10n_ch_ISR_NUMBER_LENGTH = 27
l10n_ch_ISR_NUMBER_ISSUER_LENGTH = 12

class AccountInvoice(models.Model):
    _inherit = 'account.invoice'

    l10n_ch_isr_postal = fields.Char(compute='_compute_l10n_ch_isr_postal', help='The postal reference identifying the bank managing this ISR.')
    l10n_ch_isr_postal_formatted = fields.Char(compute='_compute_l10n_ch_isr_postal', help="Postal reference of the bank, formated with '-' and without the padding zeros, to generate ISR report.")

    l10n_ch_isr_number = fields.Char(compute='_compute_l10n_ch_isr_number', store=True, help='The reference number associated with this invoice')
    l10n_ch_isr_number_spaced = fields.Char(compute='_compute_l10n_ch_isr_number', help="ISR number split in blocks of 5 characters (right-justified), to generate ISR report.")

    l10n_ch_isr_optical_line = fields.Char(compute="_compute_l10n_ch_isr_optical_line", help='Optical reading line, as it will be printed on ISR')

    l10n_ch_isr_valid = fields.Boolean(compute='_compute_l10n_ch_isr_valid', help='Boolean value. True iff all the data required to generate the ISR are present')

    l10n_ch_isr_sent = fields.Boolean(defaut=False, help="Boolean value telling whether or not the ISR corresponding to this invoice has already been printed or sent by mail.")
    l10n_ch_currency_name = fields.Char(related='currency_id.name', help="The name of this invoice's currency") #This field is used in the "invisible" condition field of the 'Print ISR' button.

    @api.depends('partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
    def _compute_l10n_ch_isr_postal(self):
        """ Computes the postal reference identifying the bank managing this ISR and formats it accordingly"""
        def _format_isr_postal(isr_postal):
            #format the isr as per specifications
            currency_code = isr_postal[:2]
            middle_part = isr_postal[2:-1]
            trailing_cipher = isr_postal[-1]
            middle_part = re.sub('^0*', '', middle_part)
            return currency_code + '-' + middle_part + '-' + trailing_cipher

        for record in self:
            if record.partner_bank_id and record.partner_bank_id.bank_id:
                isr_postal = False
                if record.currency_id.name == 'EUR':
                    isr_postal = record.partner_bank_id.bank_id.l10n_ch_postal_eur
                elif record.currency_id.name == 'CHF':
                    isr_postal = record.partner_bank_id.bank_id.l10n_ch_postal_chf
                else:
                    #we don't format if in another currency as EUR or CHF
                    continue

                if isr_postal:
                    record.l10n_ch_isr_postal = isr_postal
                    record.l10n_ch_isr_postal_formatted = _format_isr_postal(isr_postal)

    @api.depends('number', 'partner_bank_id.l10n_ch_postal')
    def _compute_l10n_ch_isr_number(self):
        """ The ISR reference number is 27 characters long. The first 12 of them
        contain the postal account number of this ISR's issuer, removing the zeros
        at the beginning and filling the empty places with zeros on the right if it is
        too short. The 15 other characters contain an internal reference identifying
        the invoice. For this, we use the invoice sequence number, removing each
        of its non-digit characters, and pad the unused spaces on the left of
        this number with zeros.
        """
        def _space_isr_number(isr_number):
            to_treat = isr_number
            res = ''
            while to_treat:
                res = to_treat[-5:] + res
                to_treat = to_treat[:-5]
                if to_treat:
                    res = ' ' + res
            return res

        for record in self:
            if record.number and record.partner_bank_id and record.partner_bank_id.l10n_ch_postal:
                invoice_issuer_ref = re.sub('^0*', '', record.partner_bank_id.l10n_ch_postal)
                invoice_issuer_ref = invoice_issuer_ref.ljust(l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0')
                invoice_ref = re.sub('[^\d]', '', record.number)
                #We only keep the last digits of the sequence number if it is too long
                invoice_ref = invoice_ref[-l10n_ch_ISR_NUMBER_ISSUER_LENGTH:]
                internal_ref = invoice_ref.zfill(l10n_ch_ISR_NUMBER_LENGTH - l10n_ch_ISR_NUMBER_ISSUER_LENGTH)

                record.l10n_ch_isr_number = invoice_issuer_ref + internal_ref
                record.l10n_ch_isr_number_spaced = _space_isr_number(record.l10n_ch_isr_number)

    @api.depends('currency_id.name', 'amount_total', 'partner_bank_id.bank_id', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
    def _compute_l10n_ch_isr_optical_line(self):
        """ The optical reading line of the ISR looks like this :
                left>isr_ref+ bank_ref>

           Where:
           - left is composed of two ciphers indicating the currency (01 for CHF,
           03 for EUR), followed by ten characters containing the total of the
           invoice (with the dot between units and cents removed, everything being
           right-aligned and empty places filled with zeros). After the total,
           left contains a last cipher, which is the result of a recursive modulo
           10 function ran over the rest of it.

            - isr_ref is the ISR reference number

            - bank_ref is the full postal bank code (aka clearing number) of the
            bank supporting the ISR (including the zeros).
        """
        for record in self:
            if record.l10n_ch_isr_number and record.l10n_ch_isr_postal and record.currency_id.name:
                #Left part
                currency_code = None
                if record.currency_id.name == 'CHF':
                    currency_code = '01'
                elif record.currency_id.name == 'EUR':
                    currency_code = '03'
                units, cents = float_split_str(record.amount_total, 2)
                amount_to_display = units + cents
                amount_ref = amount_to_display.zfill(10)
                left = currency_code + amount_ref
                left = mod10r(left)
                #Final assembly (the space after the '+' is no typo, it stands in the specs.)
                record.l10n_ch_isr_optical_line = left + '>' + record.l10n_ch_isr_number + '+ ' + record.l10n_ch_isr_postal + '>'

    @api.depends('type', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id', 'currency_id.name', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
    def _compute_l10n_ch_isr_valid(self):
        """Returns True if all the data required to generate the ISR are present"""
        for record in self:
            record.l10n_ch_isr_valid = record.type == 'out_invoice' and\
                record.number and \
                record.l10n_ch_isr_postal and \
                record.partner_bank_id and \
                record.partner_bank_id.l10n_ch_postal and \
                record.l10n_ch_currency_name in ['EUR', 'CHF']

    def split_total_amount(self):
       """ Splits the total amount of this invoice in two parts, using the dot as
       a separator, and taking two precision digits (always displayed).
       These two parts are returned as the two elements of a tuple, as strings
       to print in the report.

       This function is needed on the model, as it must be called in the report
       template, which cannot reference static functions
       """
       return float_split_str(self.amount_total, 2)

    def isr_print(self):
        """ Triggered by the 'Print ISR' button.
        """
        self.ensure_one()
        if self.l10n_ch_isr_valid:
            self.l10n_ch_isr_sent = True
            return self.env['report'].get_action(self, 'l10n_ch.isr_report_main')
        else:
           raise ValidationError(_("""You cannot generate an ISR yet.\n
                                   For this, you need to :\n
                                   - set a valid postal account number (or an IBAN referencing one) for your company\n
                                   - define its bank\n
                                   - associate this bank with a postal reference for the currency used in this invoice\n
                                   - fill the 'bank account' field of the invoice with the postal to be used to receive the related payment. A default account will be automatically set for all invoices created after you defined a postal account for your company."""))

    def action_invoice_sent(self):
        """ Overridden. Triggered by the 'send by mail' button.
        """
        rslt = super(AccountInvoice, self).action_invoice_sent()

        if self.l10n_ch_isr_valid:
            rslt['context']['l10n_ch_mark_isr_as_sent'] = True

        return rslt
<commit_msg>[FIX] l10n_ch: the ISR number was missing a mod10r check character in its last position<commit_after># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import re

from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_split_str
from odoo.tools.misc import mod10r

l10n_ch_ISR_NUMBER_LENGTH = 27
l10n_ch_ISR_NUMBER_ISSUER_LENGTH = 12

class AccountInvoice(models.Model):
    _inherit = 'account.invoice'

    l10n_ch_isr_postal = fields.Char(compute='_compute_l10n_ch_isr_postal', help='The postal reference identifying the bank managing this ISR.')
    l10n_ch_isr_postal_formatted = fields.Char(compute='_compute_l10n_ch_isr_postal', help="Postal reference of the bank, formated with '-' and without the padding zeros, to generate ISR report.")

    l10n_ch_isr_number = fields.Char(compute='_compute_l10n_ch_isr_number', store=True, help='The reference number associated with this invoice')
    l10n_ch_isr_number_spaced = fields.Char(compute='_compute_l10n_ch_isr_number', help="ISR number split in blocks of 5 characters (right-justified), to generate ISR report.")

    l10n_ch_isr_optical_line = fields.Char(compute="_compute_l10n_ch_isr_optical_line", help='Optical reading line, as it will be printed on ISR')

    l10n_ch_isr_valid = fields.Boolean(compute='_compute_l10n_ch_isr_valid', help='Boolean value. True iff all the data required to generate the ISR are present')

    l10n_ch_isr_sent = fields.Boolean(defaut=False, help="Boolean value telling whether or not the ISR corresponding to this invoice has already been printed or sent by mail.")
    l10n_ch_currency_name = fields.Char(related='currency_id.name', help="The name of this invoice's currency") #This field is used in the "invisible" condition field of the 'Print ISR' button.

    @api.depends('partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
    def _compute_l10n_ch_isr_postal(self):
        """ Computes the postal reference identifying the bank managing this ISR and formats it accordingly"""
        def _format_isr_postal(isr_postal):
            #format the isr as per specifications
            currency_code = isr_postal[:2]
            middle_part = isr_postal[2:-1]
            trailing_cipher = isr_postal[-1]
            middle_part = re.sub('^0*', '', middle_part)
            return currency_code + '-' + middle_part + '-' + trailing_cipher

        for record in self:
            if record.partner_bank_id and record.partner_bank_id.bank_id:
                isr_postal = False
                if record.currency_id.name == 'EUR':
                    isr_postal = record.partner_bank_id.bank_id.l10n_ch_postal_eur
                elif record.currency_id.name == 'CHF':
                    isr_postal = record.partner_bank_id.bank_id.l10n_ch_postal_chf
                else:
                    #we don't format if in another currency as EUR or CHF
                    continue

                if isr_postal:
                    record.l10n_ch_isr_postal = isr_postal
                    record.l10n_ch_isr_postal_formatted = _format_isr_postal(isr_postal)

    @api.depends('number', 'partner_bank_id.l10n_ch_postal')
    def _compute_l10n_ch_isr_number(self):
        """ The ISR reference number is 27 characters long. The first 12 of them
        contain the postal account number of this ISR's issuer, removing the zeros
        at the beginning and filling the empty places with zeros on the right if it is
        too short. The next 14 characters contain an internal reference identifying
        the invoice. For this, we use the invoice sequence number, removing each
        of its non-digit characters, and pad the unused spaces on the left of
        this number with zeros. The last character of the ISR number is the result
        of a recursive modulo 10 on its first 26 characters.
        """
        def _space_isr_number(isr_number):
            to_treat = isr_number
            res = ''
            while to_treat:
                res = to_treat[-5:] + res
                to_treat = to_treat[:-5]
                if to_treat:
                    res = ' ' + res
            return res

        for record in self:
            if record.number and record.partner_bank_id and record.partner_bank_id.l10n_ch_postal:
                invoice_issuer_ref = re.sub('^0*', '', record.partner_bank_id.l10n_ch_postal)
                invoice_issuer_ref = invoice_issuer_ref.ljust(l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0')
                invoice_ref = re.sub('[^\d]', '', record.number)
                #We only keep the last digits of the sequence number if it is too long
                invoice_ref = invoice_ref[-l10n_ch_ISR_NUMBER_ISSUER_LENGTH:]
                internal_ref = invoice_ref.zfill(l10n_ch_ISR_NUMBER_LENGTH - l10n_ch_ISR_NUMBER_ISSUER_LENGTH - 1) # -1 for mod10r check character

                record.l10n_ch_isr_number = mod10r(invoice_issuer_ref + internal_ref)
                record.l10n_ch_isr_number_spaced = _space_isr_number(record.l10n_ch_isr_number)

    @api.depends('currency_id.name', 'amount_total', 'partner_bank_id.bank_id', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
    def _compute_l10n_ch_isr_optical_line(self):
        """ The optical reading line of the ISR looks like this :
                left>isr_ref+ bank_ref>

           Where:
           - left is composed of two ciphers indicating the currency (01 for CHF,
           03 for EUR), followed by ten characters containing the total of the
           invoice (with the dot between units and cents removed, everything being
           right-aligned and empty places filled with zeros). After the total,
           left contains a last cipher, which is the result of a recursive modulo
           10 function ran over the rest of it.

            - isr_ref is the ISR reference number

            - bank_ref is the full postal bank code (aka clearing number) of the
            bank supporting the ISR (including the zeros).
        """
        for record in self:
            if record.l10n_ch_isr_number and record.l10n_ch_isr_postal and record.currency_id.name:
                #Left part
                currency_code = None
                if record.currency_id.name == 'CHF':
                    currency_code = '01'
                elif record.currency_id.name == 'EUR':
                    currency_code = '03'
                units, cents = float_split_str(record.amount_total, 2)
                amount_to_display = units + cents
                amount_ref = amount_to_display.zfill(10)
                left = currency_code + amount_ref
                left = mod10r(left)
                #Final assembly (the space after the '+' is no typo, it stands in the specs.)
                record.l10n_ch_isr_optical_line = left + '>' + record.l10n_ch_isr_number + '+ ' + record.l10n_ch_isr_postal + '>'

    @api.depends('type', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id', 'currency_id.name', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
    def _compute_l10n_ch_isr_valid(self):
        """Returns True if all the data required to generate the ISR are present"""
        for record in self:
            record.l10n_ch_isr_valid = record.type == 'out_invoice' and\
                record.number and \
                record.l10n_ch_isr_postal and \
                record.partner_bank_id and \
                record.partner_bank_id.l10n_ch_postal and \
                record.l10n_ch_currency_name in ['EUR', 'CHF']

    def split_total_amount(self):
       """ Splits the total amount of this invoice in two parts, using the dot as
       a separator, and taking two precision digits (always displayed).
       These two parts are returned as the two elements of a tuple, as strings
       to print in the report.

       This function is needed on the model, as it must be called in the report
       template, which cannot reference static functions
       """
       return float_split_str(self.amount_total, 2)

    def isr_print(self):
        """ Triggered by the 'Print ISR' button.
        """
        self.ensure_one()
        if self.l10n_ch_isr_valid:
            self.l10n_ch_isr_sent = True
            return self.env['report'].get_action(self, 'l10n_ch.isr_report_main')
        else:
           raise ValidationError(_("""You cannot generate an ISR yet.\n
                                   For this, you need to :\n
                                   - set a valid postal account number (or an IBAN referencing one) for your company\n
                                   - define its bank\n
                                   - associate this bank with a postal reference for the currency used in this invoice\n
                                   - fill the 'bank account' field of the invoice with the postal to be used to receive the related payment. A default account will be automatically set for all invoices created after you defined a postal account for your company."""))

    def action_invoice_sent(self):
        """ Overridden. Triggered by the 'send by mail' button.
        """
        rslt = super(AccountInvoice, self).action_invoice_sent()

        if self.l10n_ch_isr_valid:
            rslt['context']['l10n_ch_mark_isr_as_sent'] = True

        return rslt
<|endoftext|>


<commit_before>#!/usr/bin/python
import redis
import json
import cgi

getp = cgi.FieldStorage();

baseKey = "sensordata.shackspace.20745965.data."
sensors = ["L1.Power","L2.Power","L3.Power"]
numValues = 500

if "n" in getp:
	numValues = int(getp["n"].value)

rc = redis.Redis("glados.shack")

# ensure all queries are atomic so the time values are sync
pipe = rc.pipeline(transaction=True)

for sensor in sensors:
	# get the numValues latest values
	pipe.lrange(baseKey+sensor, -numValues, -1)

res = pipe.execute()

output = {}

# convert time on the fly
latest = json.loads(res[0][-1])[0]
output["Seconds ago"] = map(lambda kv: json.loads(kv)[0]-latest , res[0])

for idx, sensordata in enumerate(res):
	output[sensors[idx]] = map(lambda kv: json.loads(kv)[1] , sensordata)

from operator import add
output["Total"] = map(	add, map(	add, output[sensors[0]], output[sensors[1]]), output[sensors[2]] )

# off we go
print "Content-Type: application/json"
print
print json.dumps(output)
<commit_msg>documentation<commit_after>#!/usr/bin/python
#
# get the latest power consumption values from redis in JSON format
# pass parameter ?n=<number> to control the number of items returned
#
# {"Total": [3734, ...], "Seconds ago": [0, ...], "L1.Power": [1612, ...], "L3.Power": [1363, ...], "L2.Power": [759, ...]}
#

import redis
import json
import cgi

getp = cgi.FieldStorage();

baseKey = "sensordata.shackspace.20745965.data."
sensors = ["L1.Power","L2.Power","L3.Power"]
numValues = 500

if "n" in getp:
	numValues = int(getp["n"].value)

rc = redis.Redis("glados.shack")

# ensure all queries are atomic so the time values are sync
pipe = rc.pipeline(transaction=True)

for sensor in sensors:
	# get the numValues latest values
	pipe.lrange(baseKey+sensor, -numValues, -1)

res = pipe.execute()

output = {}

# convert time on the fly
latest = json.loads(res[0][-1])[0]
output["Seconds ago"] = map(lambda kv: json.loads(kv)[0]-latest , res[0])

for idx, sensordata in enumerate(res):
	output[sensors[idx]] = map(lambda kv: json.loads(kv)[1] , sensordata)

from operator import add
output["Total"] = map(	add, map(	add, output[sensors[0]], output[sensors[1]]), output[sensors[2]] )

# off we go
print "Content-Type: application/json"
print
print json.dumps(output)
<|endoftext|>


<commit_before>'''
Thermostat

AppDaemon module to set thermostat based on presence and weather
conditions.  Uses input_number instances in frontend to determine
desired set points for Home, Away, Sleep
--------------------------
appdaemon.yaml setup example:

thermostat:
    module: thermostat
    class: Thermostat
    constrain_input_boolean: input_boolean.thermostat_enable
    climate: climate.thermostat_name
    sensors: sensor.whatever, sensor.another

'''

import appdaemon.plugins.hass.hassapi as hass
import datetime as dt
import pytz

DEBUG = False

AC_THRESHOLD = 75
HEAT_THRESHOLD = 57
MAX_DEWPOINT = 60
SLEEP_TIME = [5, 21]

THERMOSTAT = 'climate.thermostat_73_5c_83'

def utc_to_est(datetime_obj):
    """Naive timezone conversion because I'm lazy AF."""
    tz_offset = dt.timedelta(hours=-5)
    return datetime_obj + tz_offset

class Thermostat(hass.Hass):

    def initialize(self):
        self.ac = {
            'home': float(self.get_state('input_number.ac_home')),
            'away': float(self.get_state('input_number.ac_away')),
            'sleep': float(self.get_state('input_number.ac_sleep'))
        }
        self.heat = {
            'home': float(self.get_state('input_number.heat_home')),
            'away': float(self.get_state('input_number.heat_away')),
            'sleep': float(self.get_state('input_number.heat_sleep'))
        }

        self.current_time = utc_to_est(dt.datetime.now()).time()

        self.run_minutely(self.update_thermostat, self.current_time)

        for value in self.split_device_list(self.args['input_number']):
            self.listen_state(self.update_on_change, value)

    def update_on_change(self, entity, attribute, old, new, kwargs):
        if DEBUG:
            self.log("Updating thermostat on change")
        self.get_input_numbers()
        self.update_thermostat(None)

    def get_input_numbers(self):
        self.ac = {
            'home': float(self.get_state('input_number.ac_home')),
            'away': float(self.get_state('input_number.ac_away')),
            'sleep': float(self.get_state('input_number.ac_sleep'))
        }
        self.heat = {
            'home': float(self.get_state('input_number.heat_home')),
            'away': float(self.get_state('input_number.heat_away')),
            'sleep': float(self.get_state('input_number.heat_sleep'))
        }
        return None

    def get_desired_mode(self):
        current_temp = float(self.get_state('sensor.dark_sky_temperature'))
        current_feel = float(self.get_state('sensor.dark_sky_apparent_temperature'))
        current_dewpoint = float(self.get_state('sensor.dark_sky_dew_point'))
        if current_temp >= AC_THRESHOLD:
            return 'cool'
        elif current_dewpoint >= MAX_DEWPOINT and current_feel >= AC_THRESHOLD:
            return 'cool'
        elif current_temp <= HEAT_THRESHOLD:
            return 'heat'
        return 'off'

    def get_state_key(self):
        is_home = (self.get_state('sensor.occupancy') == 'home')
        guest = (self.get_state('input_boolean.guest_mode') == 'on')
        on_the_way_home = (self.get_state('input_boolean.on_the_way_home') == 'on')
        now = utc_to_est(dt.datetime.now())
        if now.hour <= SLEEP_TIME[0] or now.hour >= SLEEP_TIME[1]:
            return 'sleep'
        elif is_home or guest or on_the_way_home:
            return 'home'
        return 'away'

    def get_target_temp(self, state_key, mode):
        if mode == 'cool':
            return self.ac[state_key]
        elif mode == 'heat':
            return self.heat[state_key]
        return None

    def set_temperature(self, temp, mode):
        if mode == 'off':
            return self.call_service('climate/turn_off', entity_id=THERMOSTAT)

        return self.call_service('climate/set_temperature', entity_id=THERMOSTAT, temperature=temp, hvac_mode=mode)

    def set_mode(self, mode='off', **kwargs):
        if mode == 'off':
            return self.call_service('climate/turn_off', entity_id=THERMOSTAT)

        return self.call_service('climate/set_hvac_mode', entity_id=THERMOSTAT, hvac_mode=mode)

    def update_thermostat(self, time, **kwargs):
        self.get_input_numbers()
        mode = self.get_desired_mode()
        state_key = self.get_state_key()
        target_temp = self.get_target_temp(state_key, mode)

        current_mode = self.get_state(THERMOSTAT)
        current_temp = self.get_state(THERMOSTAT, attribute='temperature')
        if DEBUG:
            self.log("Current Mode={}, Mode={}, State={}, Target={}, Current Temp={}".format(current_mode, mode, state_key, target_temp, current_temp))

        if current_mode != mode or target_temp != current_temp:
            self.set_mode(mode=mode)
            if target_temp is not None:
                self.set_temperature(target_temp, mode)
            if DEBUG:
                self.log("Called thermostat service.")

<commit_msg>Changed heat thresh<commit_after>'''
Thermostat

AppDaemon module to set thermostat based on presence and weather
conditions.  Uses input_number instances in frontend to determine
desired set points for Home, Away, Sleep
--------------------------
appdaemon.yaml setup example:

thermostat:
    module: thermostat
    class: Thermostat
    constrain_input_boolean: input_boolean.thermostat_enable
    climate: climate.thermostat_name
    sensors: sensor.whatever, sensor.another

'''

import appdaemon.plugins.hass.hassapi as hass
import datetime as dt
import pytz

DEBUG = False

AC_THRESHOLD = 75
HEAT_THRESHOLD = 55
MAX_DEWPOINT = 60
SLEEP_TIME = [5, 21]

THERMOSTAT = 'climate.thermostat_73_5c_83'

def utc_to_est(datetime_obj):
    """Naive timezone conversion because I'm lazy AF."""
    tz_offset = dt.timedelta(hours=-5)
    return datetime_obj + tz_offset

class Thermostat(hass.Hass):

    def initialize(self):
        self.ac = {
            'home': float(self.get_state('input_number.ac_home')),
            'away': float(self.get_state('input_number.ac_away')),
            'sleep': float(self.get_state('input_number.ac_sleep'))
        }
        self.heat = {
            'home': float(self.get_state('input_number.heat_home')),
            'away': float(self.get_state('input_number.heat_away')),
            'sleep': float(self.get_state('input_number.heat_sleep'))
        }

        self.current_time = utc_to_est(dt.datetime.now()).time()

        self.run_minutely(self.update_thermostat, self.current_time)

        for value in self.split_device_list(self.args['input_number']):
            self.listen_state(self.update_on_change, value)

    def update_on_change(self, entity, attribute, old, new, kwargs):
        if DEBUG:
            self.log("Updating thermostat on change")
        self.get_input_numbers()
        self.update_thermostat(None)

    def get_input_numbers(self):
        self.ac = {
            'home': float(self.get_state('input_number.ac_home')),
            'away': float(self.get_state('input_number.ac_away')),
            'sleep': float(self.get_state('input_number.ac_sleep'))
        }
        self.heat = {
            'home': float(self.get_state('input_number.heat_home')),
            'away': float(self.get_state('input_number.heat_away')),
            'sleep': float(self.get_state('input_number.heat_sleep'))
        }
        return None

    def get_desired_mode(self):
        current_temp = float(self.get_state('sensor.dark_sky_temperature'))
        current_feel = float(self.get_state('sensor.dark_sky_apparent_temperature'))
        current_dewpoint = float(self.get_state('sensor.dark_sky_dew_point'))
        if current_temp >= AC_THRESHOLD:
            return 'cool'
        elif current_dewpoint >= MAX_DEWPOINT and current_feel >= AC_THRESHOLD:
            return 'cool'
        elif current_temp <= HEAT_THRESHOLD:
            return 'heat'
        return 'off'

    def get_state_key(self):
        is_home = (self.get_state('sensor.occupancy') == 'home')
        guest = (self.get_state('input_boolean.guest_mode') == 'on')
        on_the_way_home = (self.get_state('input_boolean.on_the_way_home') == 'on')
        now = utc_to_est(dt.datetime.now())
        if now.hour <= SLEEP_TIME[0] or now.hour >= SLEEP_TIME[1]:
            return 'sleep'
        elif is_home or guest or on_the_way_home:
            return 'home'
        return 'away'

    def get_target_temp(self, state_key, mode):
        if mode == 'cool':
            return self.ac[state_key]
        elif mode == 'heat':
            return self.heat[state_key]
        return None

    def set_temperature(self, temp, mode):
        if mode == 'off':
            return self.call_service('climate/turn_off', entity_id=THERMOSTAT)

        return self.call_service('climate/set_temperature', entity_id=THERMOSTAT, temperature=temp, hvac_mode=mode)

    def set_mode(self, mode='off', **kwargs):
        if mode == 'off':
            return self.call_service('climate/turn_off', entity_id=THERMOSTAT)

        return self.call_service('climate/set_hvac_mode', entity_id=THERMOSTAT, hvac_mode=mode)

    def update_thermostat(self, time, **kwargs):
        self.get_input_numbers()
        mode = self.get_desired_mode()
        state_key = self.get_state_key()
        target_temp = self.get_target_temp(state_key, mode)

        current_mode = self.get_state(THERMOSTAT)
        current_temp = self.get_state(THERMOSTAT, attribute='temperature')
        if DEBUG:
            self.log("Current Mode={}, Mode={}, State={}, Target={}, Current Temp={}".format(current_mode, mode, state_key, target_temp, current_temp))

        if current_mode != mode or target_temp != current_temp:
            self.set_mode(mode=mode)
            if target_temp is not None:
                self.set_temperature(target_temp, mode)
            if DEBUG:
                self.log("Called thermostat service.")

<|endoftext|>


<commit_before>import re
import cStringIO

from django import template
from django.contrib.sites.models import Site
from django.utils.html import escape

from adgeletti.models import AdPosition

register = template.Library()

ADS = '_adgeletti_ads'
FIRED = '_adgeletti_fired'
BREAKPOINTS = '_adgeletti_breakpoints'

def error(text):
    """Formats error text as an (escaped) HTML comment.
    """
    return '\n' % escape(text)

@register.tag(name='ad')
def parse_ad(parser, token):
    """Parser for ad tag. Usage:
        {% ad SLOT BREAKPOINT [BREAKPOINT ...] %}
    """
    args = token.split_contents()

    if len(args) < 3:
        raise template.TemplateSyntaxError(u'usage: {% ad SLOT BREAKPOINT [BREAKPOINT ...] %}')

    slot = args[1]
    breakpoints = args[2:]
    return AdNode(slot, breakpoints)

class AdNode(template.Node):
    """Emits a div with a unique id for each ad possible at the tag's location.
    """
    _clean = re.compile(r'[^-_a-zA-Z0-9]')
    _replace = u'-'

    def __init__(self, slot, breakpoints):
        self.slot = slot
        self.breakpoints = breakpoints

    @staticmethod
    def clean_value(value):
        """Escapes and cleans a value for use as the value of an attribute in
        the ad's div tag.
        """
        return escape(AdNode._clean.sub(AdNode._replace, value))

    @staticmethod
    def div_id(slot, breakpoint):
        """Returns a string (unescaped) that may be used as the id attribute for
        an ad's div.
        """
        return u'adgeletti-ad-div-%s-%s' % (slot, breakpoint)

    @staticmethod
    def build_div(div_id):
        """Builds an empty div into which an ad is to be placed.
        """
        div_id = AdNode.clean_value(div_id)
        return '

\n' % div_id

    def render(self, context):
        if context.render_context.get(FIRED, False):
            return error(u'{% ad ... %} used after {% adgeletti_go %} used')

        if ADS not in context.render_context:
            context.render_context[ADS] = {}
            context.render_context[FIRED] = False
            context.render_context[BREAKPOINTS] = set([])

        if self.slot not in context.render_context[ADS]:
            context.render_context[ADS][self.slot] = {}

        buf = cStringIO.StringIO()

        for breakpoint in self.breakpoints:
            div_id = AdNode.div_id(self.slot, breakpoint)
            div = AdNode.build_div(div_id)

            # Add breakpoint to global set
            context.render_context[BREAKPOINTS].add(breakpoint)

            # Add to context and output
            if breakpoint not in context.render_context[ADS][self.slot]:
                context.render_context[ADS][self.slot][breakpoint] = div_id
                buf.write(div)

        content = buf.getvalue()
        buf.close()

        return content

@register.tag(name='adgeletti_go')
def parse_adgeletti_go(parser, token):
    """Parser for adgeletti_go tag. Usage:
        {% adgeletti_go %}
    """
    return AdBlock()

class AdBlock(template.Node):
    """Emits a \n')
        content = buf.getvalue()
        buf.close()

        return content
<commit_msg>Added forgotten import<commit_after>import re
import json
import cStringIO

from django import template
from django.contrib.sites.models import Site
from django.utils.html import escape

from adgeletti.models import AdPosition

register = template.Library()

ADS = '_adgeletti_ads'
FIRED = '_adgeletti_fired'
BREAKPOINTS = '_adgeletti_breakpoints'

def error(text):
    """Formats error text as an (escaped) HTML comment.
    """
    return '\n' % escape(text)

@register.tag(name='ad')
def parse_ad(parser, token):
    """Parser for ad tag. Usage:
        {% ad SLOT BREAKPOINT [BREAKPOINT ...] %}
    """
    args = token.split_contents()

    if len(args) < 3:
        raise template.TemplateSyntaxError(u'usage: {% ad SLOT BREAKPOINT [BREAKPOINT ...] %}')

    slot = args[1]
    breakpoints = args[2:]
    return AdNode(slot, breakpoints)

class AdNode(template.Node):
    """Emits a div with a unique id for each ad possible at the tag's location.
    """
    _clean = re.compile(r'[^-_a-zA-Z0-9]')
    _replace = u'-'

    def __init__(self, slot, breakpoints):
        self.slot = slot
        self.breakpoints = breakpoints

    @staticmethod
    def clean_value(value):
        """Escapes and cleans a value for use as the value of an attribute in
        the ad's div tag.
        """
        return escape(AdNode._clean.sub(AdNode._replace, value))

    @staticmethod
    def div_id(slot, breakpoint):
        """Returns a string (unescaped) that may be used as the id attribute for
        an ad's div.
        """
        return u'adgeletti-ad-div-%s-%s' % (slot, breakpoint)

    @staticmethod
    def build_div(div_id):
        """Builds an empty div into which an ad is to be placed.
        """
        div_id = AdNode.clean_value(div_id)
        return '

\n' % div_id

    def render(self, context):
        if context.render_context.get(FIRED, False):
            return error(u'{% ad ... %} used after {% adgeletti_go %} used')

        if ADS not in context.render_context:
            context.render_context[ADS] = {}
            context.render_context[FIRED] = False
            context.render_context[BREAKPOINTS] = set([])

        if self.slot not in context.render_context[ADS]:
            context.render_context[ADS][self.slot] = {}

        buf = cStringIO.StringIO()

        for breakpoint in self.breakpoints:
            div_id = AdNode.div_id(self.slot, breakpoint)
            div = AdNode.build_div(div_id)

            # Add breakpoint to global set
            context.render_context[BREAKPOINTS].add(breakpoint)

            # Add to context and output
            if breakpoint not in context.render_context[ADS][self.slot]:
                context.render_context[ADS][self.slot][breakpoint] = div_id
                buf.write(div)

        content = buf.getvalue()
        buf.close()

        return content

@register.tag(name='adgeletti_go')
def parse_adgeletti_go(parser, token):
    """Parser for adgeletti_go tag. Usage:
        {% adgeletti_go %}
    """
    return AdBlock()

class AdBlock(template.Node):
    """Emits a \n')
        content = buf.getvalue()
        buf.close()

        return content
<|endoftext|>