<commit_before># (c) Crown Owned Copyright, 2016. Dstl.

import csv

from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta, SU
from unittest import mock

from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils.timezone import now, make_aware

from django_webtest import WebTest

from ..models import Link
from .common import make_user, login_user

class LinkUsageModelTest(TestCase):
    def setUp(self):
        self.user = make_user()
        self.link = Link.objects.create(
            name='Link Linkerly',
            destination='link.com',
            owner=self.user,
            is_external=False,
        )
        self.other_link = Link.objects.create(
            name='Other Link',
            destination='otherlink.com',
            owner=self.user,
            is_external=True,
        )
        self.now = now()

    def test_usage(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        # register usage
        self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_only_yesterday(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage yesterday
            mock_now.return_value = self.now - timedelta(days=1)
            self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 0)
        self.assertEquals(self.link.usage_past_seven_days(), 1)
        self.assertEquals(self.link.usage_past_thirty_days(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_six_days_ago(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage six days ago
            mock_now.return_value = self.now - timedelta(days=6)
            self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 0)
        self.assertEquals(self.link.usage_past_seven_days(), 1)
        self.assertEquals(self.link.usage_past_thirty_days(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_from_last_week(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage on last Sunday (doesn't count as "this week")
            mock_now.return_value = self.now + relativedelta(weekday=SU(-1))
            self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 0)
        self.assertEquals(self.link.usage_this_week(), 0)
        self.assertEquals(self.link.usage_past_seven_days(), 1)
        self.assertEquals(self.link.usage_past_thirty_days(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_this_week(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage on a specific Tuesday
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)

            # test this counts as "this week" on the following Thursday
            mock_now.return_value = make_aware(datetime(2016, 3, 3, 12, 0, 0))

            self.assertEquals(self.link.usage_today(), 0)
            self.assertEquals(self.link.usage_this_week(), 1)
            self.assertEquals(self.link.usage_past_seven_days(), 1)
            self.assertEquals(self.link.usage_past_thirty_days(), 1)
            self.assertEquals(self.link.usage_total(), 1)
            self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_this_month(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage in a specific month
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)

            # register usage in the previous month
            mock_now.return_value = make_aware(datetime(2016, 2, 29, 10, 0, 0))
            self.link.register_usage(self.user)

            # test it from the point of view of the specific month
            mock_now.return_value = make_aware(datetime(2016, 3, 3, 12, 0, 0))

            self.assertEquals(self.link.usage_today(), 0)
            self.assertEquals(self.link.usage_this_week(), 2)
            self.assertEquals(self.link.usage_past_seven_days(), 2)
            self.assertEquals(self.link.usage_past_thirty_days(), 2)
            self.assertEquals(self.link.usage_this_month(), 1)
            self.assertEquals(self.link.usage_total(), 2)
            self.assertEquals(self.other_link.usage_total(), 0)

class LinkUsageWebTest(WebTest):
    def setUp(self):
        self.user = make_user()
        self.assertTrue(login_user(self, self.user))

        self.link = Link.objects.create(
            name='Link Linkerly',
            destination='/',
            owner=self.user,
            is_external=False,
        )
        self.other_link = Link.objects.create(
            name='Other Link',
            destination='/links',
            owner=self.user,
            is_external=True,
        )
        self.now = now()

    def test_internal_link_usage(self):
        detail_url = reverse('link-detail', kwargs={'pk': self.link.pk})

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Link Linkerly')
        self.assertEquals(usage_today, '0')

        #   Check that this link *doesn't* appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertIsNone(user_response.html.find(id='top_links_for_user'))

        # going to the tool registers usage
        response.click(linkid='link_follow_button').follow()

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Link Linkerly')
        self.assertEquals(usage_today, '1')

        #   Check that this link does appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertEquals(
            user_response.html.find(id='top_links_for_user').find('a').text,
            'Link Linkerly'
        )

    def test_external_link_usage(self):
        detail_url = reverse('link-detail', kwargs={'pk': self.other_link.pk})

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Other Link')
        self.assertEquals(usage_today, '0')

        #   Check that this link *doesn't* appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertIsNone(user_response.html.find(id='top_links_for_user'))

        # going to the interstitial page does not register usage
        response.click(linkid='link_follow_button').follow()

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Other Link')
        self.assertEquals(usage_today, '0')

        # clicking 'yes' on interstitial does register usage
        interstitial = response.click(linkid='link_follow_button').follow()
        interstitial.form.submit()

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Other Link')
        self.assertEquals(usage_today, '1')

        #   Check that this link does appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertEquals(
            user_response.html.find(id='top_links_for_user').find('a').text,
            'Other Link'
        )

    def test_link_stats_page(self):
        self.link.register_usage(self.user)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage eight days ago
            mock_now.return_value = self.now - relativedelta(days=8)
            self.link.register_usage(self.user)

            # register usage thirty-eight days ago
            mock_now.return_value = self.now - relativedelta(days=38)
            self.link.register_usage(self.user)

        stats_url = reverse('link-stats', kwargs={'pk': self.link.pk})
        response = self.app.get(stats_url)

        self.assertEquals(
            response.html.find(class_='usage-seven-days').text,
            '1'
        )
        self.assertEquals(
            response.html.find(class_='usage-thirty-days').text,
            '2'
        )
        self.assertEquals(
            response.html.find(class_='usage-total').text,
            '3'
        )

    def test_link_stats_csv(self):
        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage at a specific date
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 11, 15, 0))
            self.link.register_usage(self.user)

        stats_url = reverse('link-stats-csv', kwargs={'pk': self.link.pk})
        response = self.app.get(stats_url)
        lines = response.body.decode().split("\r\n")
        dialect = csv.Sniffer().sniff(response.body.decode())
        reader = csv.DictReader(lines, dialect=dialect)

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 10:00:00',
            'Tool': 'Link Linkerly',
        })

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 11:15:00',
            'Tool': 'Link Linkerly',
        })

    def test_overall_stats_page(self):
        self.link.register_usage(self.user)
        self.other_link.register_usage(self.user)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage eight days ago
            mock_now.return_value = self.now - relativedelta(days=8)
            self.link.register_usage(self.user)

            # register usage thirty-eight days ago
            mock_now.return_value = self.now - relativedelta(days=38)
            self.link.register_usage(self.user)

        stats_url = reverse('link-overall-stats')
        response = self.app.get(stats_url)

        seven = response.html.find_all(class_='usage-seven-days')
        self.assertEquals(seven[0].text, '1')
        self.assertEquals(seven[1].text, '1')

        thirty = response.html.find_all(class_='usage-thirty-days')
        self.assertEquals(thirty[0].text, '2')
        self.assertEquals(thirty[1].text, '1')

        total = response.html.find_all(class_='usage-total')
        self.assertEquals(total[0].text, '3')
        self.assertEquals(total[1].text, '1')

    def test_overall_stats_csv(self):
        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage at specific times
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)

            mock_now.return_value = make_aware(datetime(2016, 3, 1, 11, 15, 0))
            self.other_link.register_usage(self.user)

            mock_now.return_value = make_aware(datetime(2016, 3, 1, 11, 16, 0))
            self.link.register_usage(self.user)

        stats_url = reverse('link-overall-stats-csv')
        response = self.app.get(stats_url)
        lines = response.body.decode().split("\r\n")
        dialect = csv.Sniffer().sniff(response.body.decode())
        reader = csv.DictReader(lines, dialect=dialect)

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 10:00:00',
            'Tool': 'Link Linkerly',
        })

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 11:15:00',
            'Tool': 'Other Link',
        })

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 11:16:00',
            'Tool': 'Link Linkerly',
        })
<commit_msg>Bring tests inline with new template display.<commit_after># (c) Crown Owned Copyright, 2016. Dstl.

import csv

from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta, SU
from unittest import mock

from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils.timezone import now, make_aware

from django_webtest import WebTest

from ..models import Link
from .common import make_user, login_user

class LinkUsageModelTest(TestCase):
    def setUp(self):
        self.user = make_user()
        self.link = Link.objects.create(
            name='Link Linkerly',
            destination='link.com',
            owner=self.user,
            is_external=False,
        )
        self.other_link = Link.objects.create(
            name='Other Link',
            destination='otherlink.com',
            owner=self.user,
            is_external=True,
        )
        self.now = now()

    def test_usage(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        # register usage
        self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_only_yesterday(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage yesterday
            mock_now.return_value = self.now - timedelta(days=1)
            self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 0)
        self.assertEquals(self.link.usage_past_seven_days(), 1)
        self.assertEquals(self.link.usage_past_thirty_days(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_six_days_ago(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage six days ago
            mock_now.return_value = self.now - timedelta(days=6)
            self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 0)
        self.assertEquals(self.link.usage_past_seven_days(), 1)
        self.assertEquals(self.link.usage_past_thirty_days(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_from_last_week(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage on last Sunday (doesn't count as "this week")
            mock_now.return_value = self.now + relativedelta(weekday=SU(-1))
            self.link.register_usage(self.user)

        self.assertEquals(self.link.usage_today(), 0)
        self.assertEquals(self.link.usage_this_week(), 0)
        self.assertEquals(self.link.usage_past_seven_days(), 1)
        self.assertEquals(self.link.usage_past_thirty_days(), 1)
        self.assertEquals(self.link.usage_total(), 1)
        self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_this_week(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage on a specific Tuesday
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)

            # test this counts as "this week" on the following Thursday
            mock_now.return_value = make_aware(datetime(2016, 3, 3, 12, 0, 0))

            self.assertEquals(self.link.usage_today(), 0)
            self.assertEquals(self.link.usage_this_week(), 1)
            self.assertEquals(self.link.usage_past_seven_days(), 1)
            self.assertEquals(self.link.usage_past_thirty_days(), 1)
            self.assertEquals(self.link.usage_total(), 1)
            self.assertEquals(self.other_link.usage_total(), 0)

    def test_usage_this_month(self):
        self.assertEquals(self.link.usage_total(), 0)
        self.assertEquals(self.other_link.usage_total(), 0)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage in a specific month
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)

            # register usage in the previous month
            mock_now.return_value = make_aware(datetime(2016, 2, 29, 10, 0, 0))
            self.link.register_usage(self.user)

            # test it from the point of view of the specific month
            mock_now.return_value = make_aware(datetime(2016, 3, 3, 12, 0, 0))

            self.assertEquals(self.link.usage_today(), 0)
            self.assertEquals(self.link.usage_this_week(), 2)
            self.assertEquals(self.link.usage_past_seven_days(), 2)
            self.assertEquals(self.link.usage_past_thirty_days(), 2)
            self.assertEquals(self.link.usage_this_month(), 1)
            self.assertEquals(self.link.usage_total(), 2)
            self.assertEquals(self.other_link.usage_total(), 0)

class LinkUsageWebTest(WebTest):
    def setUp(self):
        self.user = make_user()
        self.assertTrue(login_user(self, self.user))

        self.link = Link.objects.create(
            name='Link Linkerly',
            destination='/',
            owner=self.user,
            is_external=False,
        )
        self.other_link = Link.objects.create(
            name='Other Link',
            destination='/links',
            owner=self.user,
            is_external=True,
        )
        self.now = now()

    def test_internal_link_usage(self):
        detail_url = reverse('link-detail', kwargs={'pk': self.link.pk})

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Link Linkerly')
        self.assertEquals(usage_today, '0')

        #   Check that this link *doesn't* appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertIsNone(
            user_response.html.find(
                id='top_links_for_user'
            ).find('a', text='Link Linkerly')
        )

        # going to the tool registers usage
        response.click(linkid='link_follow_button').follow()

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Link Linkerly')
        self.assertEquals(usage_today, '1')

        #   Check that this link does appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertTrue(
            user_response.html.find(
                id='top_links_for_user'
            ).find('a', text='Link Linkerly')
        )

    def test_external_link_usage(self):
        detail_url = reverse('link-detail', kwargs={'pk': self.other_link.pk})

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Other Link')
        self.assertEquals(usage_today, '0')

        #   Check that this link *doesn't* appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertIsNone(
            user_response.html.find(
                id='top_links_for_user'
            ).find('a', text='Other Link')
        )

        # going to the interstitial page does not register usage
        response.click(linkid='link_follow_button').follow()

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Other Link')
        self.assertEquals(usage_today, '0')

        # clicking 'yes' on interstitial does register usage
        interstitial = response.click(linkid='link_follow_button').follow()
        interstitial.form.submit()

        response = self.app.get(detail_url)
        usage_today = response.html.find(id='usage-today').find(
            'span',
            {"class": "stat-inline"}
        ).text
        self.assertEquals(response.html.h1.text, 'Other Link')
        self.assertEquals(usage_today, '1')

        #   Check that this link does appear in the 'top tools' widget
        user_response = self.app.get(
            reverse('user-detail', kwargs={'slug': self.user.slug})
        )
        self.assertTrue(
            user_response.html.find(
                id='top_links_for_user'
            ).find('a', text='Other Link')
        )

    def test_link_stats_page(self):
        self.link.register_usage(self.user)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage eight days ago
            mock_now.return_value = self.now - relativedelta(days=8)
            self.link.register_usage(self.user)

            # register usage thirty-eight days ago
            mock_now.return_value = self.now - relativedelta(days=38)
            self.link.register_usage(self.user)

        stats_url = reverse('link-stats', kwargs={'pk': self.link.pk})
        response = self.app.get(stats_url)

        self.assertEquals(
            response.html.find(class_='usage-seven-days').text,
            '1'
        )
        self.assertEquals(
            response.html.find(class_='usage-thirty-days').text,
            '2'
        )
        self.assertEquals(
            response.html.find(class_='usage-total').text,
            '3'
        )

    def test_link_stats_csv(self):
        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage at a specific date
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 11, 15, 0))
            self.link.register_usage(self.user)

        stats_url = reverse('link-stats-csv', kwargs={'pk': self.link.pk})
        response = self.app.get(stats_url)
        lines = response.body.decode().split("\r\n")
        dialect = csv.Sniffer().sniff(response.body.decode())
        reader = csv.DictReader(lines, dialect=dialect)

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 10:00:00',
            'Tool': 'Link Linkerly',
        })

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 11:15:00',
            'Tool': 'Link Linkerly',
        })

    def test_overall_stats_page(self):
        self.link.register_usage(self.user)
        self.other_link.register_usage(self.user)

        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage eight days ago
            mock_now.return_value = self.now - relativedelta(days=8)
            self.link.register_usage(self.user)

            # register usage thirty-eight days ago
            mock_now.return_value = self.now - relativedelta(days=38)
            self.link.register_usage(self.user)

        stats_url = reverse('link-overall-stats')
        response = self.app.get(stats_url)

        seven = response.html.find_all(class_='usage-seven-days')
        self.assertEquals(seven[0].text, '1')
        self.assertEquals(seven[1].text, '1')

        thirty = response.html.find_all(class_='usage-thirty-days')
        self.assertEquals(thirty[0].text, '2')
        self.assertEquals(thirty[1].text, '1')

        total = response.html.find_all(class_='usage-total')
        self.assertEquals(total[0].text, '3')
        self.assertEquals(total[1].text, '1')

    def test_overall_stats_csv(self):
        with mock.patch('django.utils.timezone.now') as mock_now:
            # register usage at specific times
            mock_now.return_value = make_aware(datetime(2016, 3, 1, 10, 0, 0))
            self.link.register_usage(self.user)

            mock_now.return_value = make_aware(datetime(2016, 3, 1, 11, 15, 0))
            self.other_link.register_usage(self.user)

            mock_now.return_value = make_aware(datetime(2016, 3, 1, 11, 16, 0))
            self.link.register_usage(self.user)

        stats_url = reverse('link-overall-stats-csv')
        response = self.app.get(stats_url)
        lines = response.body.decode().split("\r\n")
        dialect = csv.Sniffer().sniff(response.body.decode())
        reader = csv.DictReader(lines, dialect=dialect)

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 10:00:00',
            'Tool': 'Link Linkerly',
        })

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 11:15:00',
            'Tool': 'Other Link',
        })

        row = next(reader)
        self.assertEquals(row, {
            'User': 'user0001com',
            'Date': '2016-03-01 11:16:00',
            'Tool': 'Link Linkerly',
        })
<|endoftext|>


<commit_before>from __future__ import print_function, division
from RPi import GPIO
from greenhouse_database import GreenhouseDatabase
from datetime import datetime
from time import sleep, time
import math
from sys import exit

try:
    import Adafruit_DHT
except ImportError:
    print("Adafruit DHT library missing.")
    exit(0)

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

class Greenhouse(object):
    DHT_SENSOR = Adafruit_DHT.DHT22
    DHT = 19
    SOIL = 26
    LIGHT = 18

    def __init__(self, db_path='/home/pi/.greenhouse/greenhouse.db'):
        """
        db_path defaults to /home/pi/.greenhouse/greenhouse.db
        """
        self.db = GreenhouseDatabase(db_path)

        self.darkness_level = 0.01

    @property
    def temperature(self):
        humidity, temperature = self._get_humidity_and_temperature()
        return temperature

    @property
    def humidity(self):
        humidity, temperature = self._get_humidity_and_temperature()
        return humidity

    @property
    def soil(self):
        return self._get_average_soil_moisture(5)

    @property
    def light(self):
        return self._get_average_light_level(5)

    def _get_humidity_and_temperature(self):
        humidity, temperature = Adafruit_DHT.read_retry(
            sensor=self.DHT_SENSOR,
            pin=self.DHT,
            retries=5
        )
        return (humidity, temperature)

    def _get_soil_moisture(self):
        time_taken = self._time_charging_soil_capacitor()
        totally_wet_time = 8E-6
        totally_dry_time = 0.01
        moisture = (
            math.log(time_taken / totally_dry_time) /
            math.log(totally_wet_time / totally_dry_time)
        )
        return max(0, min(1, moisture)) * 100

    def _time_charging_soil_capacitor(self):
        pin = self.SOIL
        GPIO.setup(pin, GPIO.OUT)
        GPIO.output(pin, GPIO.LOW)
        sleep(0.1)
        GPIO.setup(pin, GPIO.IN)
        start_time = time()
        end_time = time()
        max_time = 1
        while GPIO.input(pin) == GPIO.LOW and time() - start_time < max_time:
            end_time = time()
        time_taken = end_time - start_time
        return time_taken

    def _get_light_level(self):
        time_taken = self._time_charging_light_capacitor()
        value = 100 * time_taken / self.darkness_level
        return 100 - value

    def _time_charging_light_capacitor(self):
        pin = self.LIGHT
        GPIO.setup(pin, GPIO.OUT)
        GPIO.output(pin, GPIO.LOW)
        sleep(0.1)
        GPIO.setup(pin, GPIO.IN)
        start_time = time()
        end_time = time()
        while (
            GPIO.input(pin) == GPIO.LOW and
            time() - start_time < self.darkness_level
        ):
            end_time = time()
        time_taken = end_time - start_time
        return min(time_taken, self.darkness_level)

    def _get_average_soil_moisture(self, num):
        values = [self._get_soil_moisture() for n in range(num)]
        average_value = sum(values) / len(values)
        return average_value

    def _get_average_light_level(self, num):
        values = [self._get_light_level() for n in range(num)]
        average_value = sum(values) / len(values)
        return average_value

    def _get_timestamp(self):
        dt = datetime.now()
        dt_date = str(dt.date())
        dt_time = str(dt.time())
        timestamp = "%s %s" % (dt_date, dt_time[:8])
        return timestamp

    def record_sensor_values(self):
        """
        Save sensor readings to database
        """
        timestamp = self._get_timestamp()
        temperature = self.temperature
        humidity = self.humidity
        soil = self.soil
        light = self.light

        values = (timestamp, temperature, humidity, soil, light)

        self.db.record_sensor_values(values)

    def export_to_csv(self, file_path='/home/pi/greenhouse.csv'):
        """
        Export sensor data from database and save as CSV file in file_path
        Defaults to /home/pi/greenhouse.csv
        """
        self.db.export_to_csv(file_path)

def main():
    greenhouse = Greenhouse()
    greenhouse.record_sensor_values()
    greenhouse.export_to_csv()
    print("Temperature:")
    print(greenhouse.temperature)
    print("Humidity:")
    print(greenhouse.humidity)
    print("Soil:")
    print(greenhouse.soil)
    print("Light:")
    print(greenhouse.light)

if __name__ == '__main__':
    main()
<commit_msg>Update soil sensor code for new boards<commit_after>from __future__ import print_function, division
from RPi import GPIO
from greenhouse_database import GreenhouseDatabase
from datetime import datetime
from time import sleep, time
import math
from sys import exit

try:
    import Adafruit_DHT
except ImportError:
    print("Adafruit DHT library missing.")
    exit(0)

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

class Greenhouse(object):
    DHT_SENSOR = Adafruit_DHT.DHT22
    DHT = 19
    SOIL = 26
    LIGHT = 18

    def __init__(self, db_path='/home/pi/.greenhouse/greenhouse.db'):
        """
        db_path defaults to /home/pi/.greenhouse/greenhouse.db
        """
        self.db = GreenhouseDatabase(db_path)

        self.darkness_level = 0.01

    @property
    def temperature(self):
        humidity, temperature = self._get_humidity_and_temperature()
        return temperature

    @property
    def humidity(self):
        humidity, temperature = self._get_humidity_and_temperature()
        return humidity

    @property
    def soil(self):
        return self._get_average_soil_moisture(5)

    @property
    def light(self):
        return self._get_average_light_level(5)

    def _get_humidity_and_temperature(self):
        humidity, temperature = Adafruit_DHT.read_retry(
            sensor=self.DHT_SENSOR,
            pin=self.DHT,
            retries=5
        )
        return (humidity, temperature)

    def _get_soil_moisture(self):
        time_taken = self._time_charging_soil_capacitor()
        totally_wet_time = 4E-3
        totally_dry_time = 0.9
        moisture = (
            math.log(time_taken / totally_dry_time) /
            math.log(totally_wet_time / totally_dry_time)
        )
        return max(0, min(1, moisture)) * 100

    def _time_charging_soil_capacitor(self):
        pin = self.SOIL
        GPIO.setup(pin, GPIO.OUT, GPIO.PUD_OFF)
        GPIO.output(pin, GPIO.LOW)
        sleep(0.1)
        start_time = time()
        GPIO.setup(pin, GPIO.IN)
        max_time = 1
        while GPIO.input(pin) == GPIO.LOW and time() - start_time < max_time:
            pass
        end_time = time()
        time_taken = end_time - start_time
        return time_taken

    def _get_light_level(self):
        time_taken = self._time_charging_light_capacitor()
        value = 100 * time_taken / self.darkness_level
        return 100 - value

    def _time_charging_light_capacitor(self):
        pin = self.LIGHT
        GPIO.setup(pin, GPIO.OUT)
        GPIO.output(pin, GPIO.LOW)
        sleep(0.1)
        GPIO.setup(pin, GPIO.IN)
        start_time = time()
        end_time = time()
        while (
            GPIO.input(pin) == GPIO.LOW and
            time() - start_time < self.darkness_level
        ):
            end_time = time()
        time_taken = end_time - start_time
        return min(time_taken, self.darkness_level)

    def _get_average_soil_moisture(self, num):
        values = [self._get_soil_moisture() for n in range(num)]
        average_value = sum(values) / len(values)
        return average_value

    def _get_average_light_level(self, num):
        values = [self._get_light_level() for n in range(num)]
        average_value = sum(values) / len(values)
        return average_value

    def _get_timestamp(self):
        dt = datetime.now()
        dt_date = str(dt.date())
        dt_time = str(dt.time())
        timestamp = "%s %s" % (dt_date, dt_time[:8])
        return timestamp

    def record_sensor_values(self):
        """
        Save sensor readings to database
        """
        timestamp = self._get_timestamp()
        temperature = self.temperature
        humidity = self.humidity
        soil = self.soil
        light = self.light

        values = (timestamp, temperature, humidity, soil, light)

        self.db.record_sensor_values(values)

    def export_to_csv(self, file_path='/home/pi/greenhouse.csv'):
        """
        Export sensor data from database and save as CSV file in file_path
        Defaults to /home/pi/greenhouse.csv
        """
        self.db.export_to_csv(file_path)

def main():
    greenhouse = Greenhouse()
    greenhouse.record_sensor_values()
    greenhouse.export_to_csv()
    print("Temperature:")
    print(greenhouse.temperature)
    print("Humidity:")
    print(greenhouse.humidity)
    print("Soil:")
    print(greenhouse.soil)
    print("Light:")
    print(greenhouse.light)

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before># Copyright (C) 2011 Lukas Lalinsky
# Distributed under the MIT license, see the LICENSE file for details.

import sys
import logging
import sqlalchemy
import sqlalchemy.pool
from optparse import OptionParser
from acoustid.config import Config
from acoustid.indexclient import IndexClientPool
from acoustid.utils import LocalSysLogHandler

logger = logging.getLogger(__name__)

class Script(object):

    def __init__(self, config_path):
        self.config = Config(config_path)
        #self.engine = sqlalchemy.create_engine(self.config.database.create_url(),
        #    poolclass=sqlalchemy.pool.AssertionPool)
        self.engine = sqlalchemy.create_engine(self.config.database.create_url())
        if not self.config.index.host:
            self.index = None
        else:
            self.index = IndexClientPool(host=self.config.index.host,
                                         port=self.config.index.port,
                                         recycle=15)
        self.setup_logging()

    def setup_logging(self):
        for logger_name, level in sorted(self.config.logging.levels.items()):
            logging.getLogger(logger_name).setLevel(level)
        if self.config.logging.syslog:
            handler = LocalSysLogHandler(ident='acoustid',
                facility=self.config.logging.syslog_facility, log_pid=True)
            handler.setFormatter(logging.Formatter('%(name)s: %(message)s'))
            logging.getLogger().addHandler(handler)

    def setup_console_logging(self, quiet=False):
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(name)s - %(message)s', '%H:%M:%S'))
        if quiet:
            handler.setLevel(logging.ERROR)
        logging.getLogger().addHandler(handler)

def run_script(func):
    parser = OptionParser()
    parser.add_option("-c", "--config", dest="config",
        help="configuration file", metavar="FILE")
    parser.add_option("-q", "--quiet", dest="quiet", action="store_true",
        default=False, help="don't print info messages to stdout")
    (options, args) = parser.parse_args()
    if not options.config:
        parser.error('no configuration file')
    script = Script(options.config)
    script.setup_console_logging(options.quiet)
    logger.debug("Running script %s", sys.argv[0])
    try:
        func(script, options, args)
    except:
        logger.exception("Script finished %s with an exception", sys.argv[0])
        raise
    else:
        logger.debug("Script finished %s successfuly", sys.argv[0])
    script.engine.dispose()

<commit_msg>Increase recycle timeout<commit_after># Copyright (C) 2011 Lukas Lalinsky
# Distributed under the MIT license, see the LICENSE file for details.

import sys
import logging
import sqlalchemy
import sqlalchemy.pool
from optparse import OptionParser
from acoustid.config import Config
from acoustid.indexclient import IndexClientPool
from acoustid.utils import LocalSysLogHandler

logger = logging.getLogger(__name__)

class Script(object):

    def __init__(self, config_path):
        self.config = Config(config_path)
        #self.engine = sqlalchemy.create_engine(self.config.database.create_url(),
        #    poolclass=sqlalchemy.pool.AssertionPool)
        self.engine = sqlalchemy.create_engine(self.config.database.create_url())
        if not self.config.index.host:
            self.index = None
        else:
            self.index = IndexClientPool(host=self.config.index.host,
                                         port=self.config.index.port,
                                         recycle=60)
        self.setup_logging()

    def setup_logging(self):
        for logger_name, level in sorted(self.config.logging.levels.items()):
            logging.getLogger(logger_name).setLevel(level)
        if self.config.logging.syslog:
            handler = LocalSysLogHandler(ident='acoustid',
                facility=self.config.logging.syslog_facility, log_pid=True)
            handler.setFormatter(logging.Formatter('%(name)s: %(message)s'))
            logging.getLogger().addHandler(handler)

    def setup_console_logging(self, quiet=False):
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(name)s - %(message)s', '%H:%M:%S'))
        if quiet:
            handler.setLevel(logging.ERROR)
        logging.getLogger().addHandler(handler)

def run_script(func):
    parser = OptionParser()
    parser.add_option("-c", "--config", dest="config",
        help="configuration file", metavar="FILE")
    parser.add_option("-q", "--quiet", dest="quiet", action="store_true",
        default=False, help="don't print info messages to stdout")
    (options, args) = parser.parse_args()
    if not options.config:
        parser.error('no configuration file')
    script = Script(options.config)
    script.setup_console_logging(options.quiet)
    logger.debug("Running script %s", sys.argv[0])
    try:
        func(script, options, args)
    except:
        logger.exception("Script finished %s with an exception", sys.argv[0])
        raise
    else:
        logger.debug("Script finished %s successfuly", sys.argv[0])
    script.engine.dispose()

<|endoftext|>


<commit_before>import bson.json_util
import cherrypy
import json
import StringIO
import sys
import traceback

from cherrypy.lib.static import serve_file

# This function defines the structure of a service response.  Each service
# module should import this function from this package.
#
# The payload is contained in the 'result' field, while the 'error' field can
# indicate that something went wrong.  Posibly more fields could be added in the
# future.
def empty_response():
    return {'result' : None,
            'error' : None}

# Convenience functions to use in the modules - this avoids having to import the
# bson module everywhere.
#
# Take a python object, and serialize it to JSON text.
def dumps(r):
    return bson.json_util.dumps(r)

# Take a JSON text string, and extract a python object from it.
def loads(s):
    return bson.json_util.loads(s)

# 'current_dir' is used by the CherryPy config file to set the root for static
# file service.
import os.path
current_dir = os.path.dirname(os.path.abspath(__file__))

class Server(object):
    @cherrypy.expose
    def default(self, *path):
        # If there are no positional arguments, behave as though the root
        # index.html was requested.
        if len(path) == 0:
            cherrypy.log("no path")
            path = ["index.html"]

        # Check the first character of the first path component.  If it's a
        # tilde, then assume the path points into a user's home directory.
        if path[0][0] == "~":
            # Make sure there is a username after the tilde.
            if len(path[0]) == 1:
                cherrypy.log("only tilde")
                raise cherrypy.HTTPError(404)

            # Get the actual path corresponding to the tilde-name (after
            # converting the positional args tuple to a list so we can assign to
            # its elements).

            #path = list(path)
            userpath = os.path.expanduser(path[0]) + "/tangelo_html"

            # Check to make sure there was an actual user by that name - if it
            # wasn't, the function will not change the input and it will still
            # have a tilde in the first position.
            if userpath[0] == "~":
                cherrypy.log("no user: " + userpath)
                raise cherrypy.HTTPError(404)

            cherrypy.log("serving user dir: " + "/".join(path))
            return serve_file(userpath + "/" +  "/".join(path[1:]))
        else:
            cherrypy.log("serving from root: " + current_dir + "/web/" + "/".join(path))
            return serve_file(current_dir + "/web/" + "/".join(path))

    @cherrypy.expose
    def service(self, module, *pargs, **kwargs):
        # TODO(choudhury): This method should attempt to load the named module, then invoke it
        # with the given arguments.  However, if the named module is "config" or
        # something similar, the method should instead launch a special "config"
        # app, which lists the available app modules, along with docstrings or
        # similar.  It should also allow the user to add/delete search paths for
        # other modules.
        cherrypy.response.headers['Content-type'] = 'text/plain'

        # Construct a response container for reporting possible errors, as the
        # service modules themselves do.
        response = empty_response()

        # Construct import statement.
        import_string = "import modules.%s" % (module)
        try:
            exec(import_string)
        except ImportError:
            response['error'] = "tangelo: error: no such module '%s'" % (module)
            return json.dumps(response)

        # Report an error if the module has no Handler object.
        m = eval("modules.%s" % (module))
        if 'Handler' not in dir(m):
            response['error'] = "tangelo: error: no Handler class defined in module '%s'" % (module)
            return json.dumps(response)

        # Construct a Handler object from the imported module.
        handler = m.Handler()

        # Report an error if the Handler object has no go() method.
        if 'go' not in dir(handler):
            response['error'] = "tangelo: error: no method go() defined in class 'Handler' in module '%s'" % (module)
            return json.dumps(response)

        # Call the go() method of the handler object, passing it the positional
        # and keyword args that came into this method.
        try:
            return handler.go(*pargs, **kwargs)
        except Exception as e:
            # Error message.
            response['error'] = "tangelo: error: %s: %s" % (e.__class__.__name__, e.message)

            cherrypy.log("Caught exception - %s: %s" % (e.__class__.__name__, e.message))

            # Full Python traceback stack.
            s = StringIO.StringIO()
            traceback.print_exc(file=s)
            if 'traceback' in response:
                response['traceback'] += "\n" + s.getvalue()
            else:
                response['traceback'] = "\n" + s.getvalue()

            # Serialize to JSON.
            return json.dumps(response)
<commit_msg>More robust handling of user web dirs.<commit_after>import bson.json_util
import cherrypy
import json
import StringIO
import sys
import traceback

from cherrypy.lib.static import serve_file

# This function defines the structure of a service response.  Each service
# module should import this function from this package.
#
# The payload is contained in the 'result' field, while the 'error' field can
# indicate that something went wrong.  Posibly more fields could be added in the
# future.
def empty_response():
    return {'result' : None,
            'error' : None}

# Convenience functions to use in the modules - this avoids having to import the
# bson module everywhere.
#
# Take a python object, and serialize it to JSON text.
def dumps(r):
    return bson.json_util.dumps(r)

# Take a JSON text string, and extract a python object from it.
def loads(s):
    return bson.json_util.loads(s)

# 'current_dir' is used by the CherryPy config file to set the root for static
# file service.
import os.path
current_dir = os.path.dirname(os.path.abspath(__file__))

class Server(object):
    @cherrypy.expose
    def default(self, *path):
        # Convert the path argument into a list (from a tuple).
        path = list(path)

        # If there are no positional arguments, behave as though the root
        # index.html was requested.
        if len(path) == 0:
            path = ["index.html"]

        # Check the first character of the first path component.  If it's a
        # tilde, then assume the path points into a user's home directory.
        if path[0][0] == "~":
            # Only treat this component as a home directory if there is actually
            # text following the tilde (rather than making the server serve
            # files from the home directory of whatever user account it is using
            # to run).
            if len(path[0]) > 1:
                # Expand the home directory, append the tangelo_html
                # subdirectory, and then the tail of the original path.
                path = os.path.expanduser(path[0]).split("/") + ["tangelo_html"] + path[1:]
        else:
            # TODO(choudhury): check a table of configured custom mappings to
            # see if the first path component should be mapped to some other
            # filesystem path.

            # Reaching this point means the path is relative to the server's web
            # root.
            path = current_dir.split("/") + ["web"] + path

        # Form a path name from the path components.
        finalpath = "/".join(path)

        # If the path represents a directory, first check if the URL is missing
        # a trailing slash - if it is, redirect to a URL with the slash
        # appended; otherwise, append "index.html" to the path spec and
        # continue.
        if os.path.isdir(finalpath):
            if cherrypy.request.path_info[-1] != "/":
                raise cherrypy.HTTPRedirect(cherrypy.request.path_info + "/")
            else:
                finalpath += "/index.html"

        # If the home directory expansion above failed (or something else went
        # wrong), then the filepath will not be an absolute path, and we bail.
        if not os.path.isabs(finalpath):
            raise cherrypy.HTTPError(404)

        # Serve the file.
        return serve_file(finalpath)

    @cherrypy.expose
    def service(self, module, *pargs, **kwargs):
        # TODO(choudhury): This method should attempt to load the named module, then invoke it
        # with the given arguments.  However, if the named module is "config" or
        # something similar, the method should instead launch a special "config"
        # app, which lists the available app modules, along with docstrings or
        # similar.  It should also allow the user to add/delete search paths for
        # other modules.
        cherrypy.response.headers['Content-type'] = 'text/plain'

        # Construct a response container for reporting possible errors, as the
        # service modules themselves do.
        response = empty_response()

        # Construct import statement.
        import_string = "import modules.%s" % (module)
        try:
            exec(import_string)
        except ImportError:
            response['error'] = "tangelo: error: no such module '%s'" % (module)
            return json.dumps(response)

        # Report an error if the module has no Handler object.
        m = eval("modules.%s" % (module))
        if 'Handler' not in dir(m):
            response['error'] = "tangelo: error: no Handler class defined in module '%s'" % (module)
            return json.dumps(response)

        # Construct a Handler object from the imported module.
        handler = m.Handler()

        # Report an error if the Handler object has no go() method.
        if 'go' not in dir(handler):
            response['error'] = "tangelo: error: no method go() defined in class 'Handler' in module '%s'" % (module)
            return json.dumps(response)

        # Call the go() method of the handler object, passing it the positional
        # and keyword args that came into this method.
        try:
            return handler.go(*pargs, **kwargs)
        except Exception as e:
            # Error message.
            response['error'] = "tangelo: error: %s: %s" % (e.__class__.__name__, e.message)

            cherrypy.log("Caught exception - %s: %s" % (e.__class__.__name__, e.message))

            # Full Python traceback stack.
            s = StringIO.StringIO()
            traceback.print_exc(file=s)
            if 'traceback' in response:
                response['traceback'] += "\n" + s.getvalue()
            else:
                response['traceback'] = "\n" + s.getvalue()

            # Serialize to JSON.
            return json.dumps(response)
<|endoftext|>


<commit_before>#!/usr/bin/env python
from __future__ import print_function, division
import re
import sys
import hashlib
import json
import os
from collections import defaultdict
from contextlib import contextmanager
import subprocess
import time
from threading import Thread
from multiprocessing import cpu_count
from optparse import OptionParser
if sys.version_info[0] < 3:
    from Queue import Queue, LifoQueue
else:
    from queue import Queue, LifoQueue

cachename = '_fcompile_cache.json'
is_debug = os.environ.get('DEBUG')

def parse_modules(f):
    defined = set()
    used = set()
    nlines = 0
    for line in f:
        nlines += 1
        line = line.lstrip()
        if not line:
            continue
        if line[0] == '!':
            continue
        word = line[:line.find(' ')].lower()
        if word == 'module':
            module = re.match(r'module\s+(\w+)\s*', line, re.IGNORECASE).group(1)
            module = module.lower()
            if module != 'procedure':
                defined.add(module)
        elif word == 'use':
            module = re.match(r'use\s+(\w+)\s*', line, re.IGNORECASE).group(1)
            used.add(module.lower())
    used.difference_update(defined)
    return nlines, defined, used

def get_file_hash(filename, prepend=None):
    with timing('sha1'):
        h = hashlib.new('sha1')
        if prepend is not None:
            h.update(prepend)
        with open(filename, 'rb') as f:
            h.update(f.read())
        return h.hexdigest()

class Clock(object):
    def __init__(self, active=True):
        self.active = active
        self.clocks = defaultdict(float)
        self.stack = []
        self.last = None

    @contextmanager
    def __call__(self, name):
        if self.active:
            label = '>'.join(self.stack + [name])
            self.clocks[label]
            self.stack.append(name)
            tm = time.time()
        try:
            yield
        finally:
            if self.active:
                self.last = time.time()-tm
                self.clocks[label] += self.last
                self.stack.pop(-1)

    def print(self, header=None):
        if not self.active:
            return
        if header:
            print(header)
        for label, clock in sorted(self.clocks.items()):
            print(label, clock)

timing = Clock(active=is_debug)

def worker(compile_queue, result_queue):
    clock = Clock()
    while True:
        task = compile_queue.get()
        if task is None:
            break
        filename, args = task
        with clock('compilation'):
            try:
                subprocess.check_call(args)
            except subprocess.CalledProcessError:
                break
            except:
                import traceback
                traceback.print_exc()
                break
        result_queue.put((filename, clock.last))
    result_queue.put((None, clock.clocks['compilation']))

class DependencyTree(object):
    def __init__(self, tasks):
        # object attributes
        self.filenames = tasks.keys()
        self.source_modules = defaultdict(list)
        self.source_dependencies = defaultdict(list)
        self.module_dependants = defaultdict(list)
        self.source_dependants = defaultdict(list)
        self.line_numbers = {}
        # helper dictionaries
        module_sources = defaultdict(list)
        module_dependencies = defaultdict(list)
        # scan files
        for filename in self.filenames:
            with open(tasks[filename]['source']) as f:
                nlines, defined, used = parse_modules(f)
            for module in defined:
                module_sources[module].append(filename)
            module_dependencies[filename] = used
            self.line_numbers[filename] = nlines
        # deal with special modules
        for used in module_dependencies.values():
            try:
                used.remove('iso_c_binding')
            except KeyError:
                pass
        if 'mpi' not in module_sources:
            for used in module_dependencies.values():
                try:
                    used.remove('mpi')
                except KeyError:
                    pass
        # check trivial inconsistencies
        for module in list(module_sources):
            if len(module_sources[module]) > 1:
                raise RuntimeError(
                    'Module {0} defined in {1}'
                    .format(module, module_sources[module])
                )
            module_sources[module] = module_sources[module][0]
        all_used_modules = set(
            module for modules in module_dependencies.values() for module in modules
        )
        for module in all_used_modules:
            if module == 'mpi':
                continue
            if module not in module_sources:
                raise RuntimeError('No source for module {0}'.format(module))
        # populate dictionaries
        for filename, modules in module_dependencies.items():
            for module in modules:
                modulefile = module_sources[module]
                self.module_dependants[module].append(filename)
                self.source_dependants[modulefile].append(filename)
                self.source_dependencies[filename].append(modulefile)
        for module, filename in module_sources.items():
            self.source_modules[filename].append(module)

# clear line and print
def pprint(s):
    sys.stdout.write('\x1b[2K\r{0}\n'.format(s))

def build(tasks, opts):
    has_error = False
    # prepare dependency tree
    print('Scanning files...')
    with timing('scanning'):
        tree = DependencyTree(tasks)
    # get hashes of source files & args
    source_hashes = dict((
        filename,
        get_file_hash(
            tasks[filename]['source'],
            prepend=' '.join(tasks[filename]['args']).encode()
        )
    ) for filename in tree.filenames)
    # read compiled hashes
    if os.path.exists(cachename):
        with open(cachename) as f:
            compiled_hashes = json.load(f)['hashes']
    else:
        compiled_hashes = {}
    # get changed files
    changed_files = [
        filename for filename in tree.filenames
        if source_hashes[filename] != compiled_hashes.get(filename)
    ]
    # file stats
    n_all_lines = sum(tree.line_numbers[filename] for filename in changed_files)
    n_compiled_lines = 0
    n_all_files = len(changed_files)
    n_compiled_files = 0
    file_timings = {}
    print('Changed files: {0}/{1}.'.format(len(changed_files), len(tree.filenames)))
    # check if continue
    if opts.dry:
        print(changed_files)
        return
    if not changed_files:
        return
    # setup queues and workers
    queue = list(changed_files)  # local master queue of tasks
    blocking = dict(  # does file block
        (filename, filename in queue) for filename in tree.filenames
    )
    submitted = []  # local queue of submitted tasks
    compile_queue = LifoQueue()  # shared queue of tasks to be compiled asap
    result_queue = Queue()  # shared queue of results of compilation
    pool = [
        Thread(target=worker, args=(compile_queue, result_queue))
        for _ in range(opts.jobs)
    ]
    for thread in pool:
        thread.start()
    # main build loop
    start_time = time.time()
    print('Start compiling.')
    try:
        while queue + submitted:
            to_queue = []  # files to queue for compile in this cycle
            for filename in queue:
                if all(
                    not blocking[filename]
                    for filename in tree.source_dependencies[filename]
                ):
                    to_queue.append(filename)
            # compile longest files asap
            to_queue.sort(key=lambda filename: tree.line_numbers[filename])
            # queue
            for filename in to_queue:
                queue.remove(filename)
                submitted.append(filename)
                if filename in compiled_hashes:
                    del compiled_hashes[filename]  # make sure hashes are ok
                compile_queue.put((
                    filename,
                    tasks[filename]['args'] + [tasks[filename]['source']]
                ))
            if not submitted:
                continue
            with timing('waiting'):
                filename, clock = result_queue.get()  # wait for compiled files
            if filename is None:  # worker finished prematurely
                has_error = True
                result_queue.put((None, clock))
                break
            # process compiled file
            blocking[filename] = False  # unblock
            compiled_hashes[filename] = source_hashes[filename]
            n_compiled_lines += tree.line_numbers[filename]
            n_compiled_files += 1
            current_time = time.time()
            file_timings[filename] = clock
            estimated_time = (current_time-start_time)*n_all_lines/n_compiled_lines
            pprint('Compiled {0}.'.format(filename))
            submitted.remove(filename)
            # check if module files changed
            for module in tree.source_modules[filename]:
                modulefile = module + '.mod'
                modulehash = get_file_hash(modulefile)
                if modulehash != compiled_hashes.get(modulefile):
                    compiled_hashes[modulefile] = modulehash
                    # depending files are not up-to-date anymore
                    for dependant in tree.module_dependants[module]:
                        if dependant in compiled_hashes:
                            del compiled_hashes[dependant]
                    # queue depending files
                    for dependant in tree.module_dependants[module]:
                        if dependant not in queue:
                            queue.append(dependant)
                            blocking[dependant] = True  # block
                            n_all_lines += tree.line_numbers[dependant]
                            n_all_files += 1
            # print progress line
            progress_line = \
                'Progress: {5}/{6} files, {0}/{1} lines ({2:.1f}%), {3:.1f}s/{4:.1f}s' \
                .format(
                    n_compiled_lines, n_all_lines,
                    (100*n_compiled_lines)/n_all_lines,
                    current_time-start_time, estimated_time,
                    n_compiled_files, n_all_files
                )
            if is_debug:
                compile_queue_size = compile_queue.qsize()
                progress_line += \
                    ' [compile_queue: {0}, running: {1}]' \
                    .format(
                        compile_queue_size, len(submitted)-compile_queue_size
                    )
            sys.stdout.write(progress_line + '\r')
            sys.stdout.flush()
    finally:
        print()
        for _ in pool:
            compile_queue.put(None)  # terminate workers
        for _ in pool:
            while True:  # throw away compiled unprocessed files
                obj, clock = result_queue.get()
                if obj is None:
                    break
            timing.clocks['compilation'] += clock
        for thread in pool:
            thread.join()  # terminate threads
        if is_debug and n_compiled_files > 0:
            file_timings = sorted(file_timings.items(), key=lambda it: it[1])
            median_compile_time = file_timings[n_compiled_files//2][1]
            print('Median time per file: {0:.3f}s'.format(median_compile_time))
            print('Files with longest compile time:')
            for filename, clock in file_timings[:-4:-1]:
                print('    {0}: {1:.1f}s'.format(filename, clock))
        with open(cachename, 'w') as f:
            json.dump({'hashes': compiled_hashes}, f)
    return 1 if has_error else 0

if __name__ == '__main__':
    parser = OptionParser(usage='usage: fcompile.py [options] <CONFIG.json')
    parser.add_option('-j', '--jobs', type='int', default=cpu_count(), help='number of threads')
    parser.add_option('--dry', action='store_true', help='print changed files and exit')
    opts, _ = parser.parse_args()
    tasks = json.load(sys.stdin)
    try:
        with timing('all'):
            sys.exit(build(tasks, opts))
    except KeyboardInterrupt:
        sys.exit(1)
    finally:
        timing.print(header='Timing:')
<commit_msg>decrease log output<commit_after>#!/usr/bin/env python
from __future__ import print_function, division
import re
import sys
import hashlib
import json
import os
from collections import defaultdict
from contextlib import contextmanager
import subprocess
import time
from threading import Thread
from multiprocessing import cpu_count
from optparse import OptionParser
if sys.version_info[0] < 3:
    from Queue import Queue, LifoQueue
else:
    from queue import Queue, LifoQueue

cachename = '_fcompile_cache.json'
is_debug = os.environ.get('DEBUG')

def parse_modules(f):
    defined = set()
    used = set()
    nlines = 0
    for line in f:
        nlines += 1
        line = line.lstrip()
        if not line:
            continue
        if line[0] == '!':
            continue
        word = line[:line.find(' ')].lower()
        if word == 'module':
            module = re.match(r'module\s+(\w+)\s*', line, re.IGNORECASE).group(1)
            module = module.lower()
            if module != 'procedure':
                defined.add(module)
        elif word == 'use':
            module = re.match(r'use\s+(\w+)\s*', line, re.IGNORECASE).group(1)
            used.add(module.lower())
    used.difference_update(defined)
    return nlines, defined, used

def get_file_hash(filename, prepend=None):
    with timing('sha1'):
        h = hashlib.new('sha1')
        if prepend is not None:
            h.update(prepend)
        with open(filename, 'rb') as f:
            h.update(f.read())
        return h.hexdigest()

class Clock(object):
    def __init__(self, active=True):
        self.active = active
        self.clocks = defaultdict(float)
        self.stack = []
        self.last = None

    @contextmanager
    def __call__(self, name):
        if self.active:
            label = '>'.join(self.stack + [name])
            self.clocks[label]
            self.stack.append(name)
            tm = time.time()
        try:
            yield
        finally:
            if self.active:
                self.last = time.time()-tm
                self.clocks[label] += self.last
                self.stack.pop(-1)

    def print(self, header=None):
        if not self.active:
            return
        if header:
            print(header)
        for label, clock in sorted(self.clocks.items()):
            print(label, clock)

timing = Clock(active=is_debug)

def worker(compile_queue, result_queue):
    clock = Clock()
    while True:
        task = compile_queue.get()
        if task is None:
            break
        filename, args = task
        with clock('compilation'):
            try:
                subprocess.check_call(args)
            except subprocess.CalledProcessError:
                break
            except:
                import traceback
                traceback.print_exc()
                break
        result_queue.put((filename, clock.last))
    result_queue.put((None, clock.clocks['compilation']))

class DependencyTree(object):
    def __init__(self, tasks):
        # object attributes
        self.filenames = tasks.keys()
        self.source_modules = defaultdict(list)
        self.source_dependencies = defaultdict(list)
        self.module_dependants = defaultdict(list)
        self.source_dependants = defaultdict(list)
        self.line_numbers = {}
        # helper dictionaries
        module_sources = defaultdict(list)
        module_dependencies = defaultdict(list)
        # scan files
        for filename in self.filenames:
            with open(tasks[filename]['source']) as f:
                nlines, defined, used = parse_modules(f)
            for module in defined:
                module_sources[module].append(filename)
            module_dependencies[filename] = used
            self.line_numbers[filename] = nlines
        # deal with special modules
        for used in module_dependencies.values():
            try:
                used.remove('iso_c_binding')
            except KeyError:
                pass
        if 'mpi' not in module_sources:
            for used in module_dependencies.values():
                try:
                    used.remove('mpi')
                except KeyError:
                    pass
        # check trivial inconsistencies
        for module in list(module_sources):
            if len(module_sources[module]) > 1:
                raise RuntimeError(
                    'Module {0} defined in {1}'
                    .format(module, module_sources[module])
                )
            module_sources[module] = module_sources[module][0]
        all_used_modules = set(
            module for modules in module_dependencies.values() for module in modules
        )
        for module in all_used_modules:
            if module == 'mpi':
                continue
            if module not in module_sources:
                raise RuntimeError('No source for module {0}'.format(module))
        # populate dictionaries
        for filename, modules in module_dependencies.items():
            for module in modules:
                modulefile = module_sources[module]
                self.module_dependants[module].append(filename)
                self.source_dependants[modulefile].append(filename)
                self.source_dependencies[filename].append(modulefile)
        for module, filename in module_sources.items():
            self.source_modules[filename].append(module)

# clear line and print
def pprint(s):
    sys.stdout.write('\x1b[2K\r{0}\n'.format(s))

def build(tasks, opts):
    has_error = False
    # prepare dependency tree
    print('Scanning files...')
    with timing('scanning'):
        tree = DependencyTree(tasks)
    # get hashes of source files & args
    source_hashes = dict((
        filename,
        get_file_hash(
            tasks[filename]['source'],
            prepend=' '.join(tasks[filename]['args']).encode()
        )
    ) for filename in tree.filenames)
    # read compiled hashes
    if os.path.exists(cachename):
        with open(cachename) as f:
            compiled_hashes = json.load(f)['hashes']
    else:
        compiled_hashes = {}
    # get changed files
    changed_files = [
        filename for filename in tree.filenames
        if source_hashes[filename] != compiled_hashes.get(filename)
    ]
    # file stats
    n_all_lines = sum(tree.line_numbers[filename] for filename in changed_files)
    n_compiled_lines = 0
    n_all_files = len(changed_files)
    n_compiled_files = 0
    file_timings = {}
    print('Changed files: {0}/{1}.'.format(len(changed_files), len(tree.filenames)))
    # check if continue
    if opts.dry:
        print(changed_files)
        return
    if not changed_files:
        return
    # setup queues and workers
    queue = list(changed_files)  # local master queue of tasks
    blocking = dict(  # does file block
        (filename, filename in queue) for filename in tree.filenames
    )
    submitted = []  # local queue of submitted tasks
    compile_queue = LifoQueue()  # shared queue of tasks to be compiled asap
    result_queue = Queue()  # shared queue of results of compilation
    pool = [
        Thread(target=worker, args=(compile_queue, result_queue))
        for _ in range(opts.jobs)
    ]
    for thread in pool:
        thread.start()
    # main build loop
    start_time = time.time()
    try:
        while queue + submitted:
            to_queue = []  # files to queue for compile in this cycle
            for filename in queue:
                if all(
                    not blocking[filename]
                    for filename in tree.source_dependencies[filename]
                ):
                    to_queue.append(filename)
            # compile longest files asap
            to_queue.sort(key=lambda filename: tree.line_numbers[filename])
            # queue
            for filename in to_queue:
                queue.remove(filename)
                submitted.append(filename)
                if filename in compiled_hashes:
                    del compiled_hashes[filename]  # make sure hashes are ok
                compile_queue.put((
                    filename,
                    tasks[filename]['args'] + [tasks[filename]['source']]
                ))
            if not submitted:
                continue
            with timing('waiting'):
                filename, clock = result_queue.get()  # wait for compiled files
            if filename is None:  # worker finished prematurely
                has_error = True
                result_queue.put((None, clock))
                break
            # process compiled file
            blocking[filename] = False  # unblock
            compiled_hashes[filename] = source_hashes[filename]
            n_compiled_lines += tree.line_numbers[filename]
            n_compiled_files += 1
            current_time = time.time()
            file_timings[filename] = clock
            estimated_time = (current_time-start_time)*n_all_lines/n_compiled_lines
            pprint('Compiled {0}.'.format(filename))
            submitted.remove(filename)
            # check if module files changed
            for module in tree.source_modules[filename]:
                modulefile = module + '.mod'
                modulehash = get_file_hash(modulefile)
                if modulehash != compiled_hashes.get(modulefile):
                    compiled_hashes[modulefile] = modulehash
                    # depending files are not up-to-date anymore
                    for dependant in tree.module_dependants[module]:
                        if dependant in compiled_hashes:
                            del compiled_hashes[dependant]
                    # queue depending files
                    for dependant in tree.module_dependants[module]:
                        if dependant not in queue:
                            queue.append(dependant)
                            blocking[dependant] = True  # block
                            n_all_lines += tree.line_numbers[dependant]
                            n_all_files += 1
            # print progress line
            progress_line = \
                'Progress: {5}/{6} files, {0}/{1} lines ({2:.1f}%), {3:.1f}s/{4:.1f}s' \
                .format(
                    n_compiled_lines, n_all_lines,
                    (100*n_compiled_lines)/n_all_lines,
                    current_time-start_time, estimated_time,
                    n_compiled_files, n_all_files
                )
            if is_debug:
                compile_queue_size = compile_queue.qsize()
                progress_line += \
                    ' [compile_queue: {0}, running: {1}]' \
                    .format(
                        compile_queue_size, len(submitted)-compile_queue_size
                    )
            sys.stdout.write(progress_line + '\r')
            sys.stdout.flush()
    finally:
        print()
        for _ in pool:
            compile_queue.put(None)  # terminate workers
        for _ in pool:
            while True:  # throw away compiled unprocessed files
                obj, clock = result_queue.get()
                if obj is None:
                    break
            timing.clocks['compilation'] += clock
        for thread in pool:
            thread.join()  # terminate threads
        if is_debug and n_compiled_files > 0:
            file_timings = sorted(file_timings.items(), key=lambda it: it[1])
            median_compile_time = file_timings[n_compiled_files//2][1]
            print('Median time per file: {0:.3f}s'.format(median_compile_time))
            print('Files with longest compile time:')
            for filename, clock in file_timings[:-4:-1]:
                print('    {0}: {1:.1f}s'.format(filename, clock))
        with open(cachename, 'w') as f:
            json.dump({'hashes': compiled_hashes}, f)
    return 1 if has_error else 0

if __name__ == '__main__':
    parser = OptionParser(usage='usage: fcompile.py [options] <CONFIG.json')
    parser.add_option('-j', '--jobs', type='int', default=cpu_count(), help='number of threads')
    parser.add_option('--dry', action='store_true', help='print changed files and exit')
    opts, _ = parser.parse_args()
    tasks = json.load(sys.stdin)
    try:
        with timing('all'):
            sys.exit(build(tasks, opts))
    except KeyboardInterrupt:
        sys.exit(1)
    finally:
        timing.print(header='Timing:')
<|endoftext|>


<commit_before>"""
Django settings for sendcertified project.

Generated by 'django-admin startproject' using Django 1.11.1.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os
import dj_database_url

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("SECRET_KEY")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get("DEBUG", False)

ALLOWED_HOSTS = [".herokuapp.com"]

# Application definition

INSTALLED_APPS = [
    'main.apps.MainConfig',
    'widget_tweaks',
    'tinymce',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'sendcertified.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'sendcertified.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

if DEBUG:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'sendcertified',
            'HOST': '127.0.0.1',
            'PORT': '5432',
        }
    }
else:
    DATABASES = {}
    DATABASES['default'] = dj_database_url.config(conn_max_age=600)

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'

STATIC_URL = '/static/'
<commit_msg>Update settings.py<commit_after>"""
Django settings for sendcertified project.

Generated by 'django-admin startproject' using Django 1.11.1.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os
import dj_database_url

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("SECRET_KEY")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get("DEBUG", False)

ALLOWED_HOSTS = [".herokuapp.com"]

# Application definition

INSTALLED_APPS = [
    'main.apps.MainConfig',
    'widget_tweaks',
    'tinymce',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'sendcertified.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'sendcertified.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

if DEBUG:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'sendcertified',
            'HOST': '127.0.0.1',
            'PORT': '5432',
        }
    }
else:
    DATABASES = {}
    DATABASES['default'] = dj_database_url.config(conn_max_age=600)

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(PROJECT_ROOT, 'static'),
)

STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
<|endoftext|>