<commit_before>import hashlib
import re
import hmac
from urllib.parse import urlencode

from django.conf import settings
from django.template.defaultfilters import slugify
from django.template.loader import render_to_string
from django.utils.crypto import constant_time_compare
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _

from froide.helper.text_utils import replace_custom, replace_word
from froide.helper.db_utils import save_obj_unique
from froide.helper.email_sending import send_mail

from .models import User
from . import account_activated

def get_user_for_email(email):
    try:
        return User.objects.get(email=email)
    except User.DoesNotExist:
        return False

class AccountService(object):
    def __init__(self, user):
        self.user = user

    @classmethod
    def get_username_base(self, firstname, lastname):
        base = ""
        first = slugify(firstname)
        last = slugify(lastname)
        if first and last:
            base = "%s.%s" % (first[0], last)
        elif last:
            base = last
        elif first:
            base = first
        else:
            base = "user"
        base = base[:27]
        return base

    @classmethod
    def create_user(cls, **data):
        existing_user = get_user_for_email(data['user_email'])
        if existing_user:
            return existing_user, False

        user = User(
            first_name=data['first_name'],
            last_name=data['last_name'],
            email=data['user_email']
        )
        username_base = cls.get_username_base(user.first_name, user.last_name)

        user.is_active = False
        if 'password' in data:
            user.set_password(data['password'])

        user.private = data['private']

        for key in ('address', 'organization', 'organization_url'):
            setattr(user, key, data.get(key, ''))

        # ensure username is unique
        user.username = username_base
        save_obj_unique(user, 'username', postfix_format='_{count}')

        return user, True

    def confirm_account(self, secret, request_id=None):
        if not self.check_confirmation_secret(secret, request_id):
            return False
        self.user.is_active = True
        self.user.save()
        account_activated.send_robust(sender=self.user)
        return True

    def get_autologin_url(self, url):
        return settings.SITE_URL + reverse('account-go', kwargs={
            "user_id": self.user.id,
            "secret": self.generate_autologin_secret(),
            "url": url
        })

    def check_autologin_secret(self, secret):
        return constant_time_compare(self.generate_autologin_secret(), secret)

    def generate_autologin_secret(self):
        to_sign = [str(self.user.pk)]
        if self.user.last_login:
            to_sign.append(self.user.last_login.strftime("%Y-%m-%dT%H:%M:%S"))
        return hmac.new(
            settings.SECRET_KEY.encode('utf-8'),
            (".".join(to_sign)).encode('utf-8'),
            digestmod=hashlib.md5
        ).hexdigest()

    def check_confirmation_secret(self, secret, *args):
        return constant_time_compare(
            secret,
            self.generate_confirmation_secret(*args)
        )

    def generate_confirmation_secret(self, *args):
        if self.user.email is None:
            return ''
        to_sign = [str(self.user.pk), self.user.email]
        for a in args:
            to_sign.append(str(a))
        if self.user.last_login:
            to_sign.append(self.user.last_login.strftime("%Y-%m-%dT%H:%M:%S"))
        return hmac.new(
            settings.SECRET_KEY.encode('utf-8'),
            (".".join(to_sign)).encode('utf-8'),
            digestmod=hashlib.md5
        ).hexdigest()

    def send_confirmation_mail(self, request_id=None, reference=None,
                               redirect_url=None):
        secret = self.generate_confirmation_secret(request_id)
        url_kwargs = {"user_id": self.user.pk, "secret": secret}
        if request_id:
            url_kwargs['request_id'] = request_id
        url = reverse('account-confirm', kwargs=url_kwargs)

        params = {}
        if reference:
            params['ref'] = reference.encode('utf-8')
        if redirect_url:
            params['next'] = redirect_url.encode('utf-8')
        if params:
            url = '%s?%s' % (url, urlencode(params))

        templates = []
        html_templates = []
        subject_templates = []
        if reference is not None:
            ref = reference.split(':', 1)[0]
            template_name = 'account/emails/{}/confirmation_mail'.format(
                ref
            )
            templates.append(template_name + '.txt')
            html_templates.append(template_name + '.html')
            subject_templates.append(
                'account/emails/{}/confirmation_mail_subject.txt'.format(ref)
            )

        templates.append('account/emails/confirmation_mail.txt')
        html_templates.append('account/emails/confirmation_mail.html')
        subject_templates.append('account/emails/confirmation_mail_subject.txt')

        context = {
            'url': settings.SITE_URL + url,
            'name': self.user.get_full_name(),
            'site_name': settings.SITE_NAME,
            'site_url': settings.SITE_URL
        }

        message = render_to_string(templates, context)
        html_message = render_to_string(html_templates, context)

        subject = render_to_string(subject_templates, context)

        self.user.send_mail(
            subject, message,
            ignore_active=True,
            html=html_message,
            priority=True
        )

    def send_confirm_action_mail(self, url, title, reference=None, redirect_url=None,
                                 template='account/emails/confirm_action.txt'):
        secret_url = self.get_autologin_url(url)

        params = {}
        if reference:
            params['ref'] = reference.encode('utf-8')
        if redirect_url:
            params['next'] = redirect_url.encode('utf-8')
        if params:
            secret_url = '%s?%s' % (secret_url, urlencode(params))

        message = render_to_string(
            template, {
                'url': secret_url,
                'title': title,
                'name': self.user.get_full_name(),
                'site_name': settings.SITE_NAME,
                'site_url': settings.SITE_URL
            }
        )

        # Translators: Mail subject
        subject = str(_("%(site_name)s: please confirm your action") % {
            "site_name": settings.SITE_NAME
        })
        self.user.send_mail(
            subject,
            message,
            priority=True
        )

    def send_reminder_mail(self, reference=None, redirect_url=None,
                           template='account/emails/account_reminder.txt'):
        secret_url = self.get_autologin_url(reverse('account-show'))

        message = render_to_string(
            template, {
                'url': secret_url,
                'name': self.user.get_full_name(),
                'site_name': settings.SITE_NAME,
                'site_url': settings.SITE_URL
            }
        )

        # Translators: Mail subject
        subject = str(_("%(site_name)s: account reminder") % {
            "site_name": settings.SITE_NAME
        }),
        self.user.send_mail(
            subject,
            message,
            priority=True
        )

    def send_email_change_mail(self, email):
        secret = self.generate_confirmation_secret(email)
        url_kwargs = {
            "user_id": self.user.pk,
            "secret": secret,
            "email": email
        }
        url = '%s%s?%s' % (
            settings.SITE_URL,
            reverse('account-change_email'),
            urlencode(url_kwargs)
        )
        message = render_to_string('account/emails/change_email.txt', {
            'url': url,
            'name': self.user.get_full_name(),
            'site_name': settings.SITE_NAME,
            'site_url': settings.SITE_URL
        })
        # Translators: Mail subject
        subject = str(_("%(site_name)s: please confirm your new email address") % {
            "site_name": settings.SITE_NAME
        })
        send_mail(
            subject,
            message,
            email,
            priority=True
        )

    def apply_name_redaction(self, content, replacement=''):
        if not self.user.private:
            return content

        if self.user.is_deleted:
            # No more info present about user to redact
            return content

        needles = [
            self.user.last_name, self.user.first_name,
            self.user.get_full_name()
        ]
        if self.user.organization:
            needles.append(self.user.organization)

        needles = [re.escape(n) for n in needles]

        for needle in needles:
            content = re.sub(needle, replacement, content, flags=re.I | re.U)

        return content

    def apply_message_redaction(self, content, replacements=None):
        if replacements is None:
            replacements = {}

        if self.user.is_deleted:
            # No more info present about user to redact
            return content

        if self.user.address and replacements.get('address') is not False:
            for line in self.user.address.splitlines():
                if line.strip():
                    content = content.replace(line,
                            replacements.get('address',
                                str(_("<< Address removed >>")))
                    )

        if self.user.email and replacements.get('email') is not False:
            content = content.replace(self.user.email,
                    replacements.get('email',
                    str(_("<< Email removed >>")))
            )

        if not self.user.private or replacements.get('name') is False:
            return content

        name_replacement = replacements.get('name',
                str(_("<< Name removed >>")))

        content = replace_custom(settings.FROIDE_CONFIG['greetings'],
                name_replacement, content)

        content = replace_word(self.user.last_name, name_replacement, content)
        content = replace_word(self.user.first_name, name_replacement, content)
        content = replace_word(self.user.get_full_name(), name_replacement, content)

        if self.user.organization:
            content = replace_word(self.user.organization, name_replacement, content)

        return content
<commit_msg>Fix accidental tuple<commit_after>import hashlib
import re
import hmac
from urllib.parse import urlencode

from django.conf import settings
from django.template.defaultfilters import slugify
from django.template.loader import render_to_string
from django.utils.crypto import constant_time_compare
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _

from froide.helper.text_utils import replace_custom, replace_word
from froide.helper.db_utils import save_obj_unique
from froide.helper.email_sending import send_mail

from .models import User
from . import account_activated

def get_user_for_email(email):
    try:
        return User.objects.get(email=email)
    except User.DoesNotExist:
        return False

class AccountService(object):
    def __init__(self, user):
        self.user = user

    @classmethod
    def get_username_base(self, firstname, lastname):
        base = ""
        first = slugify(firstname)
        last = slugify(lastname)
        if first and last:
            base = "%s.%s" % (first[0], last)
        elif last:
            base = last
        elif first:
            base = first
        else:
            base = "user"
        base = base[:27]
        return base

    @classmethod
    def create_user(cls, **data):
        existing_user = get_user_for_email(data['user_email'])
        if existing_user:
            return existing_user, False

        user = User(
            first_name=data['first_name'],
            last_name=data['last_name'],
            email=data['user_email']
        )
        username_base = cls.get_username_base(user.first_name, user.last_name)

        user.is_active = False
        if 'password' in data:
            user.set_password(data['password'])

        user.private = data['private']

        for key in ('address', 'organization', 'organization_url'):
            setattr(user, key, data.get(key, ''))

        # ensure username is unique
        user.username = username_base
        save_obj_unique(user, 'username', postfix_format='_{count}')

        return user, True

    def confirm_account(self, secret, request_id=None):
        if not self.check_confirmation_secret(secret, request_id):
            return False
        self.user.is_active = True
        self.user.save()
        account_activated.send_robust(sender=self.user)
        return True

    def get_autologin_url(self, url):
        return settings.SITE_URL + reverse('account-go', kwargs={
            "user_id": self.user.id,
            "secret": self.generate_autologin_secret(),
            "url": url
        })

    def check_autologin_secret(self, secret):
        return constant_time_compare(self.generate_autologin_secret(), secret)

    def generate_autologin_secret(self):
        to_sign = [str(self.user.pk)]
        if self.user.last_login:
            to_sign.append(self.user.last_login.strftime("%Y-%m-%dT%H:%M:%S"))
        return hmac.new(
            settings.SECRET_KEY.encode('utf-8'),
            (".".join(to_sign)).encode('utf-8'),
            digestmod=hashlib.md5
        ).hexdigest()

    def check_confirmation_secret(self, secret, *args):
        return constant_time_compare(
            secret,
            self.generate_confirmation_secret(*args)
        )

    def generate_confirmation_secret(self, *args):
        if self.user.email is None:
            return ''
        to_sign = [str(self.user.pk), self.user.email]
        for a in args:
            to_sign.append(str(a))
        if self.user.last_login:
            to_sign.append(self.user.last_login.strftime("%Y-%m-%dT%H:%M:%S"))
        return hmac.new(
            settings.SECRET_KEY.encode('utf-8'),
            (".".join(to_sign)).encode('utf-8'),
            digestmod=hashlib.md5
        ).hexdigest()

    def send_confirmation_mail(self, request_id=None, reference=None,
                               redirect_url=None):
        secret = self.generate_confirmation_secret(request_id)
        url_kwargs = {"user_id": self.user.pk, "secret": secret}
        if request_id:
            url_kwargs['request_id'] = request_id
        url = reverse('account-confirm', kwargs=url_kwargs)

        params = {}
        if reference:
            params['ref'] = reference.encode('utf-8')
        if redirect_url:
            params['next'] = redirect_url.encode('utf-8')
        if params:
            url = '%s?%s' % (url, urlencode(params))

        templates = []
        html_templates = []
        subject_templates = []
        if reference is not None:
            ref = reference.split(':', 1)[0]
            template_name = 'account/emails/{}/confirmation_mail'.format(
                ref
            )
            templates.append(template_name + '.txt')
            html_templates.append(template_name + '.html')
            subject_templates.append(
                'account/emails/{}/confirmation_mail_subject.txt'.format(ref)
            )

        templates.append('account/emails/confirmation_mail.txt')
        html_templates.append('account/emails/confirmation_mail.html')
        subject_templates.append('account/emails/confirmation_mail_subject.txt')

        context = {
            'url': settings.SITE_URL + url,
            'name': self.user.get_full_name(),
            'site_name': settings.SITE_NAME,
            'site_url': settings.SITE_URL
        }

        message = render_to_string(templates, context)
        html_message = render_to_string(html_templates, context)

        subject = render_to_string(subject_templates, context)

        self.user.send_mail(
            subject, message,
            ignore_active=True,
            html=html_message,
            priority=True
        )

    def send_confirm_action_mail(self, url, title, reference=None, redirect_url=None,
                                 template='account/emails/confirm_action.txt'):
        secret_url = self.get_autologin_url(url)

        params = {}
        if reference:
            params['ref'] = reference.encode('utf-8')
        if redirect_url:
            params['next'] = redirect_url.encode('utf-8')
        if params:
            secret_url = '%s?%s' % (secret_url, urlencode(params))

        message = render_to_string(
            template, {
                'url': secret_url,
                'title': title,
                'name': self.user.get_full_name(),
                'site_name': settings.SITE_NAME,
                'site_url': settings.SITE_URL
            }
        )

        # Translators: Mail subject
        subject = str(_("%(site_name)s: please confirm your action") % {
            "site_name": settings.SITE_NAME
        })
        self.user.send_mail(
            subject,
            message,
            priority=True
        )

    def send_reminder_mail(self, reference=None, redirect_url=None,
                           template='account/emails/account_reminder.txt'):
        secret_url = self.get_autologin_url(reverse('account-show'))

        message = render_to_string(
            template, {
                'url': secret_url,
                'name': self.user.get_full_name(),
                'site_name': settings.SITE_NAME,
                'site_url': settings.SITE_URL
            }
        )

        # Translators: Mail subject
        subject = str(_("%(site_name)s: account reminder") % {
            "site_name": settings.SITE_NAME
        })
        self.user.send_mail(
            subject,
            message,
            priority=True
        )

    def send_email_change_mail(self, email):
        secret = self.generate_confirmation_secret(email)
        url_kwargs = {
            "user_id": self.user.pk,
            "secret": secret,
            "email": email
        }
        url = '%s%s?%s' % (
            settings.SITE_URL,
            reverse('account-change_email'),
            urlencode(url_kwargs)
        )
        message = render_to_string('account/emails/change_email.txt', {
            'url': url,
            'name': self.user.get_full_name(),
            'site_name': settings.SITE_NAME,
            'site_url': settings.SITE_URL
        })
        # Translators: Mail subject
        subject = str(_("%(site_name)s: please confirm your new email address") % {
            "site_name": settings.SITE_NAME
        })
        send_mail(
            subject,
            message,
            email,
            priority=True
        )

    def apply_name_redaction(self, content, replacement=''):
        if not self.user.private:
            return content

        if self.user.is_deleted:
            # No more info present about user to redact
            return content

        needles = [
            self.user.last_name, self.user.first_name,
            self.user.get_full_name()
        ]
        if self.user.organization:
            needles.append(self.user.organization)

        needles = [re.escape(n) for n in needles]

        for needle in needles:
            content = re.sub(needle, replacement, content, flags=re.I | re.U)

        return content

    def apply_message_redaction(self, content, replacements=None):
        if replacements is None:
            replacements = {}

        if self.user.is_deleted:
            # No more info present about user to redact
            return content

        if self.user.address and replacements.get('address') is not False:
            for line in self.user.address.splitlines():
                if line.strip():
                    content = content.replace(line,
                            replacements.get('address',
                                str(_("<< Address removed >>")))
                    )

        if self.user.email and replacements.get('email') is not False:
            content = content.replace(self.user.email,
                    replacements.get('email',
                    str(_("<< Email removed >>")))
            )

        if not self.user.private or replacements.get('name') is False:
            return content

        name_replacement = replacements.get('name',
                str(_("<< Name removed >>")))

        content = replace_custom(settings.FROIDE_CONFIG['greetings'],
                name_replacement, content)

        content = replace_word(self.user.last_name, name_replacement, content)
        content = replace_word(self.user.first_name, name_replacement, content)
        content = replace_word(self.user.get_full_name(), name_replacement, content)

        if self.user.organization:
            content = replace_word(self.user.organization, name_replacement, content)

        return content
<|endoftext|>


<commit_before>import requests
import responses
from teamscale_client.teamscale_client import TeamscaleClient

def get_client():
    return TeamscaleClient("http://localhost:8080", "admin", "admin", "foo")

@responses.activate
def test_put():
    responses.add(responses.PUT, 'http://localhost:8080',
                      body='success', status=200)
    resp = get_client().put("http://localhost:8080", "[]", {})
    assert resp.text == "success"
<commit_msg>Add basic execution tests for upload methods<commit_after>import requests
import responses
import re
from teamscale_client.teamscale_client import TeamscaleClient

URL = "http://localhost:8080"

def get_client():
    return TeamscaleClient(URL, "admin", "admin", "foo")

def get_project_service_mock(service_id):
    return re.compile(r'%s/p/foo/%s/.*' % (URL, service_id))

def get_global_service_mock(service_id):
    return re.compile(r'%s/%s/.*' % (URL, service_id))

@responses.activate
def test_put():
    responses.add(responses.PUT, 'http://localhost:8080',
                      body='success', status=200)
    resp = get_client().put("http://localhost:8080", "[]", {})
    assert resp.text == "success"

@responses.activate
def test_upload_findings():
    responses.add(responses.PUT, get_project_service_mock('add-external-findings'),
                      body='success', status=200)
    resp = get_client().upload_findings([], 18073649127634, "Test message", "partition-name")
    assert resp.text == "success"

@responses.activate
def test_upload_metrics():
    responses.add(responses.PUT, get_project_service_mock('add-external-metrics'),
                      body='success', status=200)
    resp = get_client().upload_metrics([], 18073649127634, "Test message", "partition-name")
    assert resp.text == "success"

@responses.activate
def test_upload_metric_description():
    responses.add(responses.PUT, get_global_service_mock('add-external-metric-description'),
                      body='success', status=200)
    resp = get_client().upload_metric_definitions([])
    assert resp.text == "success"

<|endoftext|>


<commit_before>from datetime import timedelta
from gi.repository import Notify
from time import time

POMO       = 1500  # 60 * 25
SHORT_REST = 300   # 60 * 5
LONG_REST  = 1800  # 60 * 30
NB_REST    = 4

class Py3status:
    """
    Pomodoro method
    """
    pomo = POMO
    rest = SHORT_REST
    nb_rest = NB_REST
    default_response = {
        'full_text': '',
        'name': 'pomodoro',
    }

    def pomodoro(self, json, i3status_config):
        """
        This function will update a timer in the i3status bar for 25 minutes,
        then print a red "Break time" message for 5 minutes.
        """
        status = self.get_status()

        if status == 'stop':
            self.pomo = POMO
            self.rest = SHORT_REST
            self.nb_rest = NB_REST
            return self.default_response
        elif status == 'pause':
            return self.default_response

        # Else run normally
        if not self.pomo:
            self.rest -= 1
            timer = self.rest
            self.full_text = 'Break time'
            color = 'color_bad'
        else:
            self.pomo -= 1
            timer = self.pomo
            self.full_text = 'Pomodoro'
            color = 'color_degraded'

        if not timer:
            # Pomodoro or Rest is finished, send notification
            self.send_notification()

        response = self.default_response.copy()
        response.update({
            'cached_until' : time(),
            'color': i3status_config[color],
            'full_text': '{}: {}'.format(
                self.full_text, str(timedelta(seconds=timer))[2:]),
        })

        return (0, response)

    def get_status(self):
        """
        Read the watchdog file and get the status value. Value is modifying via
        i3 key mapping.

        Value can be:
            * start
            * pause
            * stop (default)
        """
        pass

    def send_notification(self):
        """
        :Requirements: libnotify, python-gobject
        """

        Notify.init('Pomodoro')
        notification = Notify.Notification.new (
            'Attention:',
            '{} is finished'.format(self.full_text),
            'dialog-information',
        )
        notification.show()

        if not self.rest:
            # Rest is finished, reinitialize data
            self.nb_rest -= 1
            self.pomo = POMO
            self.rest = SHORT_REST

        if not self.nb_rest:
            # Number of small rest reached, go for long rest
            self.rest = LONG_REST
            self.nb_rest = NB_REST
<commit_msg>reading log file for value. Do I really need watchdog ?<commit_after>from datetime import timedelta
from gi.repository import Notify
import os.path
from time import time
#from time import sleep
#from watchdog.observers import Observer
#from watchdog.events import LoggingEventHandler

POMO       = 1500  # 60 * 25
SHORT_REST = 300   # 60 * 5
LONG_REST  = 1800  # 60 * 30
NB_REST    = 4
WATCHDOG_FILE = os.path.join(os.path.expanduser('~'), '.i3', 'watchdog.log')

class Py3status:
    """
    Pomodoro method
    """
    pomo = POMO
    rest = SHORT_REST
    nb_rest = NB_REST
    default_response = {
        'full_text': '',
        'name': 'pomodoro',
    }

    def pomodoro(self, json, i3status_config):
        """
        This function will update a timer in the i3status bar for 25 minutes,
        then print a red "Break time" message for 5 minutes.
        """
        status = self.get_status()

        if status == 'stop':
            self.pomo = POMO
            self.rest = SHORT_REST
            self.nb_rest = NB_REST
            return self.default_response
        elif status == 'pause':
            return self.default_response

        # Else run normally
        if not self.pomo:
            self.rest -= 1
            timer = self.rest
            self.full_text = 'Break time'
            color = 'color_bad'
        else:
            self.pomo -= 1
            timer = self.pomo
            self.full_text = 'Pomodoro'
            color = 'color_degraded'

        if not timer:
            # Pomodoro or Rest is finished, send notification
            self.send_notification()

        response = self.default_response.copy()
        response.update({
            'cached_until' : time(),
            'color': i3status_config[color],
            'full_text': '{}: {}'.format(
                self.full_text, str(timedelta(seconds=timer))[2:]),
        })

        return (0, response)

    def get_status(self):
        """
        Read the watchdog file and get the status value. Value is modifying via
        i3 key mapping.

        Value can be:
            * start
            * pause
            * stop (default)
        """
        with open(WATCHDOG_FILE, 'r') as f:
            status = f.read()

        return status

    def send_notification(self):
        """
        :Requirements: libnotify, python-gobject
        """

        Notify.init('Pomodoro')
        notification = Notify.Notification.new (
            'Attention:',
            '{} is finished'.format(self.full_text),
            'dialog-information',
        )
        notification.show()

        if not self.rest:
            # Rest is finished, reinitialize data
            self.nb_rest -= 1
            self.pomo = POMO
            self.rest = SHORT_REST

        if not self.nb_rest:
            # Number of small rest reached, go for long rest
            self.rest = LONG_REST
            self.nb_rest = NB_REST

#if __name__ == "__main__":
    #event_handler = LoggingEventHandler()
    #observer = Observer()
    #observer.schedule(event_handler, path=WATCHDOG_FILE, recursive=True)
    #observer.start()
    #try:
        #while True:
            #sleep(1)
    #except KeyboardInterrupt:
        #observer.stop()
    #observer.join()
<|endoftext|>


<commit_before>from __future__ import unicode_literals

import re

from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import PermissionDenied
from django.urls import reverse, reverse_lazy
from django.template.response import TemplateResponse
from django.contrib.admin import helpers
from django.utils.six import BytesIO
from django import forms
from django.utils.html import format_html

from froide.helper.admin_utils import (make_nullfilter, AdminTagAllMixIn,
                                      ForeignKeyFilter, TaggitListFilter)
from froide.helper.widgets import TagAutocompleteWidget
from froide.helper.forms import get_fk_form_class
from froide.helper.email_utils import EmailParser
from froide.helper.document import can_convert_to_pdf

from .models import (FoiRequest, FoiMessage, FoiProject,
        FoiAttachment, FoiEvent, PublicBodySuggestion,
        DeferredMessage, TaggedFoiRequest, RequestDraft, DeliveryStatus)
from .tasks import count_same_foirequests, convert_attachment_task

SUBJECT_REQUEST_ID = re.compile(r' \[#(\d+)\]')

class FoiMessageInline(admin.StackedInline):
    model = FoiMessage
    raw_id_fields = ('request', 'sender_user', 'sender_public_body', 'recipient_public_body')

class FoiRequestAdminForm(forms.ModelForm):
    class Meta:
        model = FoiRequest
        fields = '__all__'
        widgets = {
            'tags': TagAutocompleteWidget(
                autocomplete_url=reverse_lazy('api:request-tags-autocomplete')
            ),
        }

class FoiRequestTagsFilter(TaggitListFilter):
    tag_class = TaggedFoiRequest

class FoiRequestAdmin(admin.ModelAdmin, AdminTagAllMixIn):
    form = FoiRequestAdminForm

    prepopulated_fields = {"slug": ("title",)}
    inlines = [
        FoiMessageInline,
    ]
    list_display = ('title', 'first_message', 'secret_address', 'request_page',
                    'public_body', 'status', 'visibility')
    list_filter = ('jurisdiction', 'first_message', 'last_message', 'status',
        'resolution', 'is_foi', 'checked', 'public', 'visibility',
        'is_blocked',
        make_nullfilter('same_as', _('Has same request')),
        ('user', ForeignKeyFilter), ('public_body', ForeignKeyFilter),
        ('project', ForeignKeyFilter), FoiRequestTagsFilter
    )
    search_fields = ['title', 'description', 'secret_address', 'reference']
    ordering = ('-last_message',)
    date_hierarchy = 'first_message'

    tag_all_config = ('tags', reverse_lazy('api:request-tags-autocomplete'))

    actions = ['mark_checked', 'mark_not_foi', 'tag_all',
               'mark_same_as', 'remove_from_index', 'confirm_request',
               'set_visible_to_user', 'unpublish']
    raw_id_fields = ('same_as', 'public_body', 'user', 'project')
    save_on_top = True

    def request_page(self, obj):
        return format_html('{}',
            obj.get_absolute_url(), _('request page'))
    request_page.allow_tags = True

    def mark_checked(self, request, queryset):
        rows_updated = queryset.update(checked=True)
        self.message_user(request, _("%d request(s) successfully marked as checked." % rows_updated))
    mark_checked.short_description = _("Mark selected requests as checked")

    def mark_not_foi(self, request, queryset):
        rows_updated = queryset.update(is_foi=False)
        self.message_user(request, _("%d request(s) successfully marked as not FoI." % rows_updated))
    mark_not_foi.short_description = _("Mark selected requests as not FoI")

    def mark_same_as(self, request, queryset):
        """
        Mark selected requests as same as the one we are choosing now.

        """
        opts = self.model._meta
        # Check that the user has change permission for the actual model
        if not self.has_change_permission(request):
            raise PermissionDenied

        Form = get_fk_form_class(self.model, 'same_as', self.admin_site)
        # User has already chosen the other req
        if request.POST.get('obj'):
            f = Form(request.POST)
            if f.is_valid():
                req = f.cleaned_data['obj']
                queryset.update(same_as=req)
                count_same_foirequests.delay(req.id)
                self.message_user(request, _("Successfully marked requests as identical."))
                # Return None to display the change list page again.
                return None
        else:
            f = Form()

        context = {
            'opts': opts,
            'queryset': queryset,
            'media': self.media,
            'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
            'form': f,
            'applabel': opts.app_label
        }

        # Display the confirmation page
        return TemplateResponse(request, 'foirequest/admin_mark_same_as.html',
            context)
    mark_same_as.short_description = _("Mark selected requests as identical to...")

    def remove_from_index(self, request, queryset):
        from haystack import connections as haystack_connections

        for obj in queryset:
            for using in haystack_connections.connections_info.keys():
                backend = haystack_connections[using].get_backend()
                backend.remove(obj)

        self.message_user(request, _("Removed from search index"))
    remove_from_index.short_description = _("Remove from search index")

    def confirm_request(self, request, queryset):
        foireq = queryset[0]
        if foireq.status != 'awaiting_user_confirmation':
            self.message_user(request, _("Request not in correct state!"))
            return None
        self.message_user(request, _("Message send successfully!"))
        FoiRequest.confirmed_request(foireq.user, foireq.pk)
        return None
    confirm_request.short_description = _("Confirm request if unconfirmed")

    def set_visible_to_user(self, request, queryset):
        queryset.update(visibility=1)
        self.message_user(request, _("Selected requests are now only visible to requester."))
    set_visible_to_user.short_description = _("Set only visible to requester")

    def unpublish(self, request, queryset):
        queryset.update(public=False)
        self.message_user(request, _("Selected requests are now unpublished."))
    unpublish.short_description = _("Unpublish")

class FoiAttachmentInline(admin.TabularInline):
    model = FoiAttachment
    raw_id_fields = ('redacted', 'converted')

class DeliveryStatusInline(admin.TabularInline):
    model = DeliveryStatus
    extra = 0
    max_num = 1
    min_num = 0
    raw_id_fields = ('message',)
    readonly_fields = ('log', 'status', 'last_update')

class FoiMessageAdmin(admin.ModelAdmin):
    save_on_top = True
    list_display = ('subject', 'timestamp', 'sender_email', 'recipient_email',)
    list_filter = (
        'is_postal', 'is_response', 'sent', 'status',
        make_nullfilter('recipient_public_body', _('Has recipient public body')),
        'deliverystatus__status',
        make_nullfilter('deliverystatus', _('Has delivery status')),
        'sender_user__is_active',
        'sender_user__is_blocked',
        'sender_user__is_deleted',
    )
    search_fields = ['subject', 'sender_email', 'recipient_email']
    ordering = ('-timestamp',)
    date_hierarchy = 'timestamp'
    exclude = ('original',)
    raw_id_fields = ('request', 'sender_user', 'sender_public_body', 'recipient_public_body')
    inlines = [
        DeliveryStatusInline,
        FoiAttachmentInline,
    ]
    actions = ['check_delivery_status']

    def check_delivery_status(self, request, queryset):
        from .tasks import check_delivery_status
        for message in queryset:
            check_delivery_status.delay(message.id, extended=True)
        self.message_user(request, _("Selected messages are being checked for delivery."))
    check_delivery_status.short_description = _("Check delivery status")

class FoiAttachmentAdmin(admin.ModelAdmin):
    raw_id_fields = ('belongs_to', 'redacted', 'converted')
    ordering = ('-id',)
    list_display = ('name', 'filetype', 'admin_link_message', 'approved', 'can_approve',)
    list_filter = ('can_approve', 'approved', 'is_redacted', 'is_converted',
                   make_nullfilter('redacted', _('Has redacted version')),
                   make_nullfilter('converted', _('Has converted version'))
    )
    search_fields = ['name']
    actions = ['approve', 'cannot_approve', 'convert']

    def admin_link_message(self, obj):
        return format_html('{}',
            reverse('admin:foirequest_foimessage_change',
                args=(obj.belongs_to_id,)), _('See FoiMessage'))
    admin_link_message.allow_tags = True

    def approve(self, request, queryset):
        rows_updated = queryset.update(approved=True)
        self.message_user(request, _("%d attachment(s) successfully approved." % rows_updated))
    approve.short_description = _("Mark selected as approved")

    def cannot_approve(self, request, queryset):
        rows_updated = queryset.update(can_approve=False)
        self.message_user(request, _("%d attachment(s) successfully marked as not approvable." % rows_updated))
    cannot_approve.short_description = _("Mark selected as NOT approvable")

    def convert(self, request, queryset):
        if not queryset:
            return
        count = 0
        for instance in queryset:
            ft = instance.filetype.lower()
            name = instance.name.lower()
            if can_convert_to_pdf(ft, name=name):
                count += 1
                convert_attachment_task.delay(instance.pk)
        self.message_user(request, _("Conversion tasks started: %s") % count)
    convert.short_description = _("Convert to PDF")

class FoiEventAdmin(admin.ModelAdmin):
    list_display = ('event_name', 'request', 'timestamp',)
    list_filter = ('event_name', 'public')
    search_fields = ['request__title', "public_body__name"]
    ordering = ('-timestamp',)
    date_hierarchy = 'timestamp'
    raw_id_fields = ('request', 'user', 'public_body')

class PublicBodySuggestionAdmin(admin.ModelAdmin):
    list_display = ('request', 'public_body', 'user', 'reason',)
    search_fields = ['request', 'reason']
    ordering = ('-timestamp',)
    date_hierarchy = 'timestamp'
    raw_id_fields = ('request', 'public_body', 'user')

class DeferredMessageAdmin(admin.ModelAdmin):
    model = DeferredMessage

    list_filter = ('delivered', make_nullfilter('request', _('Has request')),
                   'spam')
    search_fields = ['recipient']
    date_hierarchy = 'timestamp'
    ordering = ('-timestamp',)
    list_display = ('recipient', 'timestamp', 'spam', 'delivered',
                    'get_email_details', 'request',)
    raw_id_fields = ('request',)
    actions = ['deliver_no_spam', 'redeliver', 'redeliver_subject']

    # Reduce per page because parsing emails is heavy
    list_per_page = 20

    save_on_top = True

    def get_email_details(self, obj):
        parser = EmailParser()
        email = parser.parse(BytesIO(obj.encoded_mail()))
        return '%s (%s...)' % (email['from'][1], email.get('subject')[:20])
    get_email_details.short_description = _('email details')

    def redeliver_subject(self, request, queryset):
        parser = EmailParser()
        for deferred in queryset:
            email = parser.parse(BytesIO(deferred.encoded_mail()))
            match = SUBJECT_REQUEST_ID.search(email['subject'])
            if match is not None:
                try:
                    req = FoiRequest.objects.get(pk=match.group(1))
                    deferred.redeliver(req)
                except FoiRequest.DoesNotExist:
                    continue
    redeliver_subject.short_description = _("Auto-Redeliver based on subject")

    def deliver_no_spam(self, request, queryset):
        for deferred in queryset:
            if deferred.request is not None:
                deferred.spam = False
                if deferred.delivered:
                    deferred.save()
                else:
                    deferred.redeliver(deferred.request)
    deliver_no_spam.short_description = _("Deliver and mark as no spam")

    def redeliver(self, request, queryset, auto=False):
        """
        Redeliver undelivered mails

        """
        opts = self.model._meta
        # Check that the user has change permission for the actual model
        if not self.has_change_permission(request):
            raise PermissionDenied

        Form = get_fk_form_class(self.model, 'request', self.admin_site)
        # User has already chosen the other req
        if request.POST.get('obj'):
            f = Form(request.POST)
            if f.is_valid():
                req = f.cleaned_data['obj']
                for deferred in queryset:
                    deferred.redeliver(req)
                self.message_user(request, _("Successfully triggered redelivery."))
                return None
        else:
            f = Form()

        context = {
            'opts': opts,
            'queryset': queryset,
            'media': self.media,
            'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
            'form': f,
            'applabel': opts.app_label
        }

        # Display the confirmation page
        return TemplateResponse(request, 'foirequest/admin_redeliver.html',
            context)
    redeliver.short_description = _("Redeliver to...")

class FoiProjectAdminForm(forms.ModelForm):
    class Meta:
        model = FoiProject
        fields = '__all__'
        widgets = {
            'tags': TagAutocompleteWidget(
                autocomplete_url=reverse_lazy('api:request-tags-autocomplete')
            ),
        }

class FoiProjectAdmin(admin.ModelAdmin):
    form = FoiRequestAdminForm

    list_display = ('title', 'created',
        'requests_admin_link',
        'user', 'public', 'status', 'request_count', 'site_link')
    list_filter = ('public', 'status',)
    search_fields = ['title', 'description', 'reference']
    ordering = ('-last_update',)
    date_hierarchy = 'created'
    raw_id_fields = ('user', 'team', 'publicbodies',)

    def site_link(self, obj):
        return format_html('{}',
            obj.get_absolute_url(),
            _('Show on site')
        )

    def requests_admin_link(self, obj):
        return format_html('{}',
            reverse('admin:foirequest_foirequest_changelist') + (
                '?project__id__exact={}'.format(obj.id)
            ),
            _('Requests in admin')
        )

class RequestDraftAdmin(admin.ModelAdmin):
    list_display = ('save_date', 'user', 'subject',)
    list_filter = ('public', 'full_text')
    search_fields = ['subject', 'user__email']
    ordering = ('-save_date',)
    date_hierarchy = 'save_date'
    raw_id_fields = ('user', 'publicbodies', 'request', 'project')

admin.site.register(FoiRequest, FoiRequestAdmin)
admin.site.register(FoiMessage, FoiMessageAdmin)
admin.site.register(FoiAttachment, FoiAttachmentAdmin)
admin.site.register(FoiEvent, FoiEventAdmin)
admin.site.register(PublicBodySuggestion, PublicBodySuggestionAdmin)
admin.site.register(DeferredMessage, DeferredMessageAdmin)
admin.site.register(RequestDraft, RequestDraftAdmin)
admin.site.register(FoiProject, FoiProjectAdmin)
<commit_msg>Add message resend admin action<commit_after>from __future__ import unicode_literals

import re

from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import PermissionDenied
from django.urls import reverse, reverse_lazy
from django.template.response import TemplateResponse
from django.contrib.admin import helpers
from django.utils.six import BytesIO
from django import forms
from django.utils.html import format_html

from froide.helper.admin_utils import (make_nullfilter, AdminTagAllMixIn,
                                      ForeignKeyFilter, TaggitListFilter)
from froide.helper.widgets import TagAutocompleteWidget
from froide.helper.forms import get_fk_form_class
from froide.helper.email_utils import EmailParser
from froide.helper.document import can_convert_to_pdf

from .models import (FoiRequest, FoiMessage, FoiProject,
        FoiAttachment, FoiEvent, PublicBodySuggestion,
        DeferredMessage, TaggedFoiRequest, RequestDraft, DeliveryStatus)
from .tasks import count_same_foirequests, convert_attachment_task

SUBJECT_REQUEST_ID = re.compile(r' \[#(\d+)\]')

class FoiMessageInline(admin.StackedInline):
    model = FoiMessage
    raw_id_fields = (
        'request', 'sender_user', 'sender_public_body', 'recipient_public_body'
    )

class FoiRequestAdminForm(forms.ModelForm):
    class Meta:
        model = FoiRequest
        fields = '__all__'
        widgets = {
            'tags': TagAutocompleteWidget(
                autocomplete_url=reverse_lazy('api:request-tags-autocomplete')
            ),
        }

class FoiRequestTagsFilter(TaggitListFilter):
    tag_class = TaggedFoiRequest

class FoiRequestAdmin(admin.ModelAdmin, AdminTagAllMixIn):
    form = FoiRequestAdminForm

    prepopulated_fields = {"slug": ("title",)}
    inlines = [
        FoiMessageInline,
    ]
    list_display = ('title', 'first_message', 'secret_address', 'request_page',
                    'public_body', 'status', 'visibility')
    list_filter = ('jurisdiction', 'first_message', 'last_message', 'status',
        'resolution', 'is_foi', 'checked', 'public', 'visibility',
        'is_blocked',
        make_nullfilter('same_as', _('Has same request')),
        ('user', ForeignKeyFilter), ('public_body', ForeignKeyFilter),
        ('project', ForeignKeyFilter), FoiRequestTagsFilter
    )
    search_fields = ['title', 'description', 'secret_address', 'reference']
    ordering = ('-last_message',)
    date_hierarchy = 'first_message'

    tag_all_config = ('tags', reverse_lazy('api:request-tags-autocomplete'))

    actions = ['mark_checked', 'mark_not_foi', 'tag_all',
               'mark_same_as', 'remove_from_index', 'confirm_request',
               'set_visible_to_user', 'unpublish']
    raw_id_fields = ('same_as', 'public_body', 'user', 'project')
    save_on_top = True

    def request_page(self, obj):
        return format_html('{}',
            obj.get_absolute_url(), _('request page'))
    request_page.allow_tags = True

    def mark_checked(self, request, queryset):
        rows_updated = queryset.update(checked=True)
        self.message_user(request,
            _("%d request(s) successfully marked as checked." % rows_updated))
    mark_checked.short_description = _("Mark selected requests as checked")

    def mark_not_foi(self, request, queryset):
        rows_updated = queryset.update(is_foi=False)
        self.message_user(request,
            _("%d request(s) successfully marked as not FoI." % rows_updated))
    mark_not_foi.short_description = _("Mark selected requests as not FoI")

    def mark_same_as(self, request, queryset):
        """
        Mark selected requests as same as the one we are choosing now.

        """
        opts = self.model._meta
        # Check that the user has change permission for the actual model
        if not self.has_change_permission(request):
            raise PermissionDenied

        Form = get_fk_form_class(self.model, 'same_as', self.admin_site)
        # User has already chosen the other req
        if request.POST.get('obj'):
            f = Form(request.POST)
            if f.is_valid():
                req = f.cleaned_data['obj']
                queryset.update(same_as=req)
                count_same_foirequests.delay(req.id)
                self.message_user(request,
                    _("Successfully marked requests as identical."))
                # Return None to display the change list page again.
                return None
        else:
            f = Form()

        context = {
            'opts': opts,
            'queryset': queryset,
            'media': self.media,
            'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
            'form': f,
            'applabel': opts.app_label
        }

        # Display the confirmation page
        return TemplateResponse(request, 'foirequest/admin_mark_same_as.html',
            context)
    mark_same_as.short_description = _("Mark selected requests as identical to...")

    def remove_from_index(self, request, queryset):
        from haystack import connections as haystack_connections

        for obj in queryset:
            for using in haystack_connections.connections_info.keys():
                backend = haystack_connections[using].get_backend()
                backend.remove(obj)

        self.message_user(request, _("Removed from search index"))
    remove_from_index.short_description = _("Remove from search index")

    def confirm_request(self, request, queryset):
        foireq = queryset[0]
        if foireq.status != 'awaiting_user_confirmation':
            self.message_user(request, _("Request not in correct state!"))
            return None
        self.message_user(request, _("Message send successfully!"))
        FoiRequest.confirmed_request(foireq.user, foireq.pk)
        return None
    confirm_request.short_description = _("Confirm request if unconfirmed")

    def set_visible_to_user(self, request, queryset):
        queryset.update(visibility=1)
        self.message_user(request,
            _("Selected requests are now only visible to requester."))
    set_visible_to_user.short_description = _("Set only visible to requester")

    def unpublish(self, request, queryset):
        queryset.update(public=False)
        self.message_user(request, _("Selected requests are now unpublished."))
    unpublish.short_description = _("Unpublish")

class FoiAttachmentInline(admin.TabularInline):
    model = FoiAttachment
    raw_id_fields = ('redacted', 'converted')

class DeliveryStatusInline(admin.TabularInline):
    model = DeliveryStatus
    extra = 0
    max_num = 1
    min_num = 0
    raw_id_fields = ('message',)
    readonly_fields = ('log', 'status', 'last_update')

class FoiMessageAdmin(admin.ModelAdmin):
    save_on_top = True
    list_display = ('subject', 'timestamp', 'sender_email', 'recipient_email',)
    list_filter = (
        'is_postal', 'is_response', 'sent', 'status',
        make_nullfilter('recipient_public_body',
                        _('Has recipient public body')),
        'deliverystatus__status',
        make_nullfilter('deliverystatus', _('Has delivery status')),
        'sender_user__is_active',
        'sender_user__is_blocked',
        'sender_user__is_deleted',
    )
    search_fields = ['subject', 'sender_email', 'recipient_email']
    ordering = ('-timestamp',)
    date_hierarchy = 'timestamp'
    exclude = ('original',)
    raw_id_fields = (
        'request', 'sender_user', 'sender_public_body', 'recipient_public_body'
    )
    inlines = [
        DeliveryStatusInline,
        FoiAttachmentInline,
    ]
    actions = ['check_delivery_status', 'resend_message']

    def check_delivery_status(self, request, queryset):
        from .tasks import check_delivery_status
        for message in queryset:
            check_delivery_status.delay(message.id, extended=True)
        self.message_user(request,
            _("Selected messages are being checked for delivery."))
    check_delivery_status.short_description = _("Check delivery status")

    def resend_message(self, request, queryset):
        count = 0
        total = len(queryset)
        queryset = queryset.exclude(recipient_email='').filter(sent=False)
        for message in queryset:
            message.send(notify=False)
            count += 1
        self.message_user(request,
            _("{num} of {total} selected messages were sent.").format(
                num=count, total=total
            ))
    resend_message.short_description = _('Resend selected messages')

class FoiAttachmentAdmin(admin.ModelAdmin):
    raw_id_fields = ('belongs_to', 'redacted', 'converted')
    ordering = ('-id',)
    list_display = ('name', 'filetype', 'admin_link_message', 'approved', 'can_approve',)
    list_filter = ('can_approve', 'approved', 'is_redacted', 'is_converted',
                   make_nullfilter('redacted', _('Has redacted version')),
                   make_nullfilter('converted', _('Has converted version'))
    )
    search_fields = ['name']
    actions = ['approve', 'cannot_approve', 'convert']

    def admin_link_message(self, obj):
        return format_html('{}',
            reverse('admin:foirequest_foimessage_change',
                args=(obj.belongs_to_id,)), _('See FoiMessage'))
    admin_link_message.allow_tags = True

    def approve(self, request, queryset):
        rows_updated = queryset.update(approved=True)
        self.message_user(request, _("%d attachment(s) successfully approved." % rows_updated))
    approve.short_description = _("Mark selected as approved")

    def cannot_approve(self, request, queryset):
        rows_updated = queryset.update(can_approve=False)
        self.message_user(request, _("%d attachment(s) successfully marked as not approvable." % rows_updated))
    cannot_approve.short_description = _("Mark selected as NOT approvable")

    def convert(self, request, queryset):
        if not queryset:
            return
        count = 0
        for instance in queryset:
            ft = instance.filetype.lower()
            name = instance.name.lower()
            if can_convert_to_pdf(ft, name=name):
                count += 1
                convert_attachment_task.delay(instance.pk)
        self.message_user(request, _("Conversion tasks started: %s") % count)
    convert.short_description = _("Convert to PDF")

class FoiEventAdmin(admin.ModelAdmin):
    list_display = ('event_name', 'request', 'timestamp',)
    list_filter = ('event_name', 'public')
    search_fields = ['request__title', "public_body__name"]
    ordering = ('-timestamp',)
    date_hierarchy = 'timestamp'
    raw_id_fields = ('request', 'user', 'public_body')

class PublicBodySuggestionAdmin(admin.ModelAdmin):
    list_display = ('request', 'public_body', 'user', 'reason',)
    search_fields = ['request', 'reason']
    ordering = ('-timestamp',)
    date_hierarchy = 'timestamp'
    raw_id_fields = ('request', 'public_body', 'user')

class DeferredMessageAdmin(admin.ModelAdmin):
    model = DeferredMessage

    list_filter = ('delivered', make_nullfilter('request', _('Has request')),
                   'spam')
    search_fields = ['recipient']
    date_hierarchy = 'timestamp'
    ordering = ('-timestamp',)
    list_display = ('recipient', 'timestamp', 'spam', 'delivered',
                    'get_email_details', 'request',)
    raw_id_fields = ('request',)
    actions = ['deliver_no_spam', 'redeliver', 'redeliver_subject']

    # Reduce per page because parsing emails is heavy
    list_per_page = 20

    save_on_top = True

    def get_email_details(self, obj):
        parser = EmailParser()
        email = parser.parse(BytesIO(obj.encoded_mail()))
        return '%s (%s...)' % (email['from'][1], email.get('subject')[:20])
    get_email_details.short_description = _('email details')

    def redeliver_subject(self, request, queryset):
        parser = EmailParser()
        for deferred in queryset:
            email = parser.parse(BytesIO(deferred.encoded_mail()))
            match = SUBJECT_REQUEST_ID.search(email['subject'])
            if match is not None:
                try:
                    req = FoiRequest.objects.get(pk=match.group(1))
                    deferred.redeliver(req)
                except FoiRequest.DoesNotExist:
                    continue
    redeliver_subject.short_description = _("Auto-Redeliver based on subject")

    def deliver_no_spam(self, request, queryset):
        for deferred in queryset:
            if deferred.request is not None:
                deferred.spam = False
                if deferred.delivered:
                    deferred.save()
                else:
                    deferred.redeliver(deferred.request)
    deliver_no_spam.short_description = _("Deliver and mark as no spam")

    def redeliver(self, request, queryset, auto=False):
        """
        Redeliver undelivered mails

        """
        opts = self.model._meta
        # Check that the user has change permission for the actual model
        if not self.has_change_permission(request):
            raise PermissionDenied

        Form = get_fk_form_class(self.model, 'request', self.admin_site)
        # User has already chosen the other req
        if request.POST.get('obj'):
            f = Form(request.POST)
            if f.is_valid():
                req = f.cleaned_data['obj']
                for deferred in queryset:
                    deferred.redeliver(req)
                self.message_user(request,
                    _("Successfully triggered redelivery."))
                return None
        else:
            f = Form()

        context = {
            'opts': opts,
            'queryset': queryset,
            'media': self.media,
            'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
            'form': f,
            'applabel': opts.app_label
        }

        # Display the confirmation page
        return TemplateResponse(request, 'foirequest/admin_redeliver.html',
            context)
    redeliver.short_description = _("Redeliver to...")

class FoiProjectAdminForm(forms.ModelForm):
    class Meta:
        model = FoiProject
        fields = '__all__'
        widgets = {
            'tags': TagAutocompleteWidget(
                autocomplete_url=reverse_lazy('api:request-tags-autocomplete')
            ),
        }

class FoiProjectAdmin(admin.ModelAdmin):
    form = FoiRequestAdminForm

    list_display = ('title', 'created',
        'requests_admin_link',
        'user', 'public', 'status', 'request_count', 'site_link')
    list_filter = ('public', 'status',)
    search_fields = ['title', 'description', 'reference']
    ordering = ('-last_update',)
    date_hierarchy = 'created'
    raw_id_fields = ('user', 'team', 'publicbodies',)

    def site_link(self, obj):
        return format_html('{}',
            obj.get_absolute_url(),
            _('Show on site')
        )

    def requests_admin_link(self, obj):
        return format_html('{}',
            reverse('admin:foirequest_foirequest_changelist') + (
                '?project__id__exact={}'.format(obj.id)
            ),
            _('Requests in admin')
        )

class RequestDraftAdmin(admin.ModelAdmin):
    list_display = ('save_date', 'user', 'subject',)
    list_filter = ('public', 'full_text')
    search_fields = ['subject', 'user__email']
    ordering = ('-save_date',)
    date_hierarchy = 'save_date'
    raw_id_fields = ('user', 'publicbodies', 'request', 'project')

admin.site.register(FoiRequest, FoiRequestAdmin)
admin.site.register(FoiMessage, FoiMessageAdmin)
admin.site.register(FoiAttachment, FoiAttachmentAdmin)
admin.site.register(FoiEvent, FoiEventAdmin)
admin.site.register(PublicBodySuggestion, PublicBodySuggestionAdmin)
admin.site.register(DeferredMessage, DeferredMessageAdmin)
admin.site.register(RequestDraft, RequestDraftAdmin)
admin.site.register(FoiProject, FoiProjectAdmin)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

import uuid
import reversion

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext as _

from apps.events.models import AttendanceEvent, Attendee
from apps.marks.models import Suspension

from rest_framework.exceptions import NotAcceptable

User = settings.AUTH_USER_MODEL

class Payment(models.Model):

    TYPE_CHOICES = (
        (1, _(u'Umiddelbar')),
        (2, _(u'Frist')),
        (3, _(u'Utsettelse')),
    )

    # creates tuples used as key choices.
    STRIPE_KEY_CHOICES = zip(settings.STRIPE_PUBLIC_KEYS.keys(), settings.STRIPE_PUBLIC_KEYS.keys())

    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()
    stripe_key = models.CharField(_(u'stripe key'),
                                  max_length=10,
                                  choices=STRIPE_KEY_CHOICES,
                                  default="arrkom"
                                  )

    payment_type = models.SmallIntegerField(_(u'type'), choices=TYPE_CHOICES)

    # Optional fields depending on payment type
    deadline = models.DateTimeField(_(u"frist"), blank=True, null=True)
    active = models.BooleanField(default=True)
    delay = models.SmallIntegerField(_(u'utsettelse'), blank=True, null=True, default=2)

    # For logging and history
    added_date = models.DateTimeField(_(u"opprettet dato"), auto_now=True)
    changed_date = models.DateTimeField(auto_now=True, editable=False)
    last_changed_by = models.ForeignKey(User, editable=False, null=True)  # Blank and null is temperarly

    def paid_users(self):
        return [payment_relation.user for payment_relation in self.paymentrelation_set.filter(refunded=False)]

    def payment_delays(self):
        return self.paymentdelay_set.filter(active=True)

    def payment_delay_users(self):
        return [payment_delay.user for payment_delay in self.payment_delays()]

    def create_payment_delay(self, user, deadline):
        payment_delays = self.paymentdelay_set.filter(payment=self, user=user)

        if payment_delays:
            for payment_delay in payment_delays:
                payment_delay.valid_to = deadline
                payment_delay.save()
        else:
            PaymentDelay.objects.create(payment=self, user=user, valid_to=deadline)

    def description(self):
        if self._is_type(AttendanceEvent):
            return self.content_object.event.title

    def responsible_mail(self):
        if self._is_type(AttendanceEvent):
            event_type = self.content_object.event.event_type
            if event_type == 1 or event_type == 4:  # Sosialt & Utflukt
                return settings.EMAIL_ARRKOM
            elif event_type == 2:  # Bedpres
                return settings.EMAIL_BEDKOM
            elif event_type == 3:  # Kurs
                return settings.EMAIL_FAGKOM
            elif event_type == 5:  # Ekskursjon
                return settings.EMAIL_EKSKOM
            else:
                return settings.DEFAULT_FROM_EMAIL
        else:
            return settings.DEFAULT_FROM_EMAIL

    def handle_payment(self, user):
        if self._is_type(AttendanceEvent):
            attendee = Attendee.objects.filter(event=self.content_object, user=user)

            # Delete payment delay objects for the user if there are any
            delays = PaymentDelay.objects.filter(payment=self, user=user)
            for delay in delays:
                delay.delete()

            # If the user is suspended because of a lack of payment the suspension is deactivated.
            suspensions = Suspension.objects.filter(payment_id=self.id, user=user)
            for suspension in suspensions:
                suspension.active = False
                suspension.save()

            if attendee:
                attendee[0].paid = True
                attendee[0].save()
            else:
                Attendee.objects.create(event=self.content_object, user=user, paid=True)

    def handle_refund(self, host, payment_relation):
        payment_relation.refunded = True
        payment_relation.save()

        if self._is_type(AttendanceEvent):
            self.content_object.notify_waiting_list(
                host=host, unattended_user=payment_relation.user)
            Attendee.objects.get(event=self.content_object,
                                 user=payment_relation.user).delete()

    def check_refund(self, payment_relation):
        if self._is_type(AttendanceEvent):
            attendance_event = self.content_object
            if attendance_event.unattend_deadline < timezone.now():
                return False, _(u"Fristen for og melde seg av har utgått")
            if len(Attendee.objects.filter(event=attendance_event, user=payment_relation.user)) == 0:
                return False, _(u"Du er ikke påmeldt dette arrangementet.")
            if attendance_event.event.event_start < timezone.now():
                return False, _(u"Dette arrangementet har allerede startet.")

            return True, ''

        return False, 'Refund checks not implemented'

    def prices(self):
        return self.paymentprice_set.all()

    def _is_type(self, model_type):
        return ContentType.objects.get_for_model(model_type) == self.content_type

    def __unicode__(self):
        return self.description()

    class Meta(object):
        unique_together = ('content_type', 'object_id')

        verbose_name = _(u"betaling")
        verbose_name_plural = _(u"betalinger")

reversion.register(Payment)

class PaymentPrice(models.Model):
    payment = models.ForeignKey(Payment)
    price = models.IntegerField(_(u"pris"))
    description = models.CharField(max_length=128, null=True, blank=True)

    def __unicode__(self):
        return str(self.price)

    class Meta(object):
        verbose_name = _(u"pris")
        verbose_name_plural = _(u"priser")

reversion.register(PaymentPrice)

class PaymentRelation(models.Model):
    payment = models.ForeignKey(Payment)
    payment_price = models.ForeignKey(PaymentPrice)
    user = models.ForeignKey(User)
    datetime = models.DateTimeField(auto_now=True)
    refunded = models.BooleanField(default=False)

    unique_id = models.CharField(max_length=128, null=True, blank=True)
    stripe_id = models.CharField(max_length=128)

    def save(self, *args, **kwargs):
        if not self.unique_id:
            self.unique_id = str(uuid.uuid4())
        super(PaymentRelation, self).save(*args, **kwargs)

    def __unicode__(self):
        return self.payment.description() + " - " + unicode(self.user)

    class Meta(object):
        verbose_name = _(u"betalingsrelasjon")
        verbose_name_plural = _(u"betalingsrelasjoner")

reversion.register(PaymentRelation)

class PaymentDelay(models.Model):
    payment = models.ForeignKey(Payment)
    user = models.ForeignKey(User)
    valid_to = models.DateTimeField()

    active = models.BooleanField(default=True)

    def __unicode__(self):
        return self.payment.description() + " - " + unicode(self.user)

    class Meta(object):
        unique_together = ('payment', 'user')

        verbose_name = _(u'betalingsutsettelse')
        verbose_name_plural = _(u'betalingsutsettelser')

reversion.register(PaymentDelay)

class PaymentTransaction(models.Model):
    user = models.ForeignKey(User)
    amount = models.IntegerField(null=True, blank=True)
    used_stripe = models.BooleanField(default=False)

    datetime = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return unicode(self.user) + " - " + unicode(self.amount) + "(" + unicode(self.datetime) + ")"

    def save(self, *args, **kwargs):
        if not self.pk:
            self.user.saldo = self.user.saldo + self.amount

            if self.user.saldo < 0:
                raise NotAcceptable("Insufficient funds")

            self.user.save()
        super(PaymentTransaction, self).save(*args, **kwargs)

    class Meta:
        ordering = ['-datetime']
        verbose_name = _(u'transaksjon')
        verbose_name_plural = _(u'transaksjoner')

reversion.register(PaymentTransaction)
<commit_msg>flake 8 fixes<commit_after># -*- coding: utf-8 -*-

import uuid
import reversion

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext as _

from apps.events.models import AttendanceEvent, Attendee
from apps.marks.models import Suspension

from rest_framework.exceptions import NotAcceptable

User = settings.AUTH_USER_MODEL

class Payment(models.Model):

    TYPE_CHOICES = (
        (1, _(u'Umiddelbar')),
        (2, _(u'Frist')),
        (3, _(u'Utsettelse')),
    )

    # creates tuples used as key choices.
    STRIPE_KEY_CHOICES = zip(settings.STRIPE_PUBLIC_KEYS.keys(), settings.STRIPE_PUBLIC_KEYS.keys())

    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()
    stripe_key = models.CharField(
        _(u'stripe key'),
        max_length=10,
        choices=STRIPE_KEY_CHOICES,
        default="arrkom"
    )

    payment_type = models.SmallIntegerField(_(u'type'), choices=TYPE_CHOICES)

    # Optional fields depending on payment type
    deadline = models.DateTimeField(_(u"frist"), blank=True, null=True)
    active = models.BooleanField(default=True)
    delay = models.SmallIntegerField(_(u'utsettelse'), blank=True, null=True, default=2)

    # For logging and history
    added_date = models.DateTimeField(_(u"opprettet dato"), auto_now=True)
    changed_date = models.DateTimeField(auto_now=True, editable=False)
    last_changed_by = models.ForeignKey(User, editable=False, null=True)  # Blank and null is temperarly

    def paid_users(self):
        return [payment_relation.user for payment_relation in self.paymentrelation_set.filter(refunded=False)]

    def payment_delays(self):
        return self.paymentdelay_set.filter(active=True)

    def payment_delay_users(self):
        return [payment_delay.user for payment_delay in self.payment_delays()]

    def create_payment_delay(self, user, deadline):
        payment_delays = self.paymentdelay_set.filter(payment=self, user=user)

        if payment_delays:
            for payment_delay in payment_delays:
                payment_delay.valid_to = deadline
                payment_delay.save()
        else:
            PaymentDelay.objects.create(payment=self, user=user, valid_to=deadline)

    def description(self):
        if self._is_type(AttendanceEvent):
            return self.content_object.event.title

    def responsible_mail(self):
        if self._is_type(AttendanceEvent):
            event_type = self.content_object.event.event_type
            if event_type == 1 or event_type == 4:  # Sosialt & Utflukt
                return settings.EMAIL_ARRKOM
            elif event_type == 2:  # Bedpres
                return settings.EMAIL_BEDKOM
            elif event_type == 3:  # Kurs
                return settings.EMAIL_FAGKOM
            elif event_type == 5:  # Ekskursjon
                return settings.EMAIL_EKSKOM
            else:
                return settings.DEFAULT_FROM_EMAIL
        else:
            return settings.DEFAULT_FROM_EMAIL

    def handle_payment(self, user):
        if self._is_type(AttendanceEvent):
            attendee = Attendee.objects.filter(event=self.content_object, user=user)

            # Delete payment delay objects for the user if there are any
            delays = PaymentDelay.objects.filter(payment=self, user=user)
            for delay in delays:
                delay.delete()

            # If the user is suspended because of a lack of payment the suspension is deactivated.
            suspensions = Suspension.objects.filter(payment_id=self.id, user=user)
            for suspension in suspensions:
                suspension.active = False
                suspension.save()

            if attendee:
                attendee[0].paid = True
                attendee[0].save()
            else:
                Attendee.objects.create(event=self.content_object, user=user, paid=True)

    def handle_refund(self, host, payment_relation):
        payment_relation.refunded = True
        payment_relation.save()

        if self._is_type(AttendanceEvent):
            self.content_object.notify_waiting_list(
                host=host, unattended_user=payment_relation.user)
            Attendee.objects.get(event=self.content_object,
                                 user=payment_relation.user).delete()

    def check_refund(self, payment_relation):
        if self._is_type(AttendanceEvent):
            attendance_event = self.content_object
            if attendance_event.unattend_deadline < timezone.now():
                return False, _(u"Fristen for og melde seg av har utgått")
            if len(Attendee.objects.filter(event=attendance_event, user=payment_relation.user)) == 0:
                return False, _(u"Du er ikke påmeldt dette arrangementet.")
            if attendance_event.event.event_start < timezone.now():
                return False, _(u"Dette arrangementet har allerede startet.")

            return True, ''

        return False, 'Refund checks not implemented'

    def prices(self):
        return self.paymentprice_set.all()

    def _is_type(self, model_type):
        return ContentType.objects.get_for_model(model_type) == self.content_type

    def __unicode__(self):
        return self.description()

    class Meta(object):
        unique_together = ('content_type', 'object_id')

        verbose_name = _(u"betaling")
        verbose_name_plural = _(u"betalinger")

reversion.register(Payment)

class PaymentPrice(models.Model):
    payment = models.ForeignKey(Payment)
    price = models.IntegerField(_(u"pris"))
    description = models.CharField(max_length=128, null=True, blank=True)

    def __unicode__(self):
        return str(self.price)

    class Meta(object):
        verbose_name = _(u"pris")
        verbose_name_plural = _(u"priser")

reversion.register(PaymentPrice)

class PaymentRelation(models.Model):
    payment = models.ForeignKey(Payment)
    payment_price = models.ForeignKey(PaymentPrice)
    user = models.ForeignKey(User)
    datetime = models.DateTimeField(auto_now=True)
    refunded = models.BooleanField(default=False)

    unique_id = models.CharField(max_length=128, null=True, blank=True)
    stripe_id = models.CharField(max_length=128)

    def save(self, *args, **kwargs):
        if not self.unique_id:
            self.unique_id = str(uuid.uuid4())
        super(PaymentRelation, self).save(*args, **kwargs)

    def __unicode__(self):
        return self.payment.description() + " - " + unicode(self.user)

    class Meta(object):
        verbose_name = _(u"betalingsrelasjon")
        verbose_name_plural = _(u"betalingsrelasjoner")

reversion.register(PaymentRelation)

class PaymentDelay(models.Model):
    payment = models.ForeignKey(Payment)
    user = models.ForeignKey(User)
    valid_to = models.DateTimeField()

    active = models.BooleanField(default=True)

    def __unicode__(self):
        return self.payment.description() + " - " + unicode(self.user)

    class Meta(object):
        unique_together = ('payment', 'user')

        verbose_name = _(u'betalingsutsettelse')
        verbose_name_plural = _(u'betalingsutsettelser')

reversion.register(PaymentDelay)

class PaymentTransaction(models.Model):
    user = models.ForeignKey(User)
    amount = models.IntegerField(null=True, blank=True)
    used_stripe = models.BooleanField(default=False)

    datetime = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return unicode(self.user) + " - " + unicode(self.amount) + "(" + unicode(self.datetime) + ")"

    def save(self, *args, **kwargs):
        if not self.pk:
            self.user.saldo = self.user.saldo + self.amount

            if self.user.saldo < 0:
                raise NotAcceptable("Insufficient funds")

            self.user.save()
        super(PaymentTransaction, self).save(*args, **kwargs)

    class Meta:
        ordering = ['-datetime']
        verbose_name = _(u'transaksjon')
        verbose_name_plural = _(u'transaksjoner')

reversion.register(PaymentTransaction)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''Chemical Engineering Design Library (ChEDL). Utilities for process modeling.
Copyright (C) 2020, Caleb Bell <Caleb.Andrew.Bell@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.'''

import pytest
import thermo
from thermo import *
import chemicals
from chemicals import *
from fluids.numerics import *
from math import *
from chemicals.utils import hash_any_primitive
import json
import os
import numpy as np

@pytest.mark.fuzz
@pytest.mark.slow
def test_ChemicalConstantsPackage_from_json_as_json_large():
    create_compounds = []
    for k in dippr_compounds():
        try:
            if search_chemical(k) is not None:
                create_compounds.append(k)
        except:
            pass

    obj = ChemicalConstantsPackage.constants_from_IDs(create_compounds)
    obj2 = ChemicalConstantsPackage.from_json(json.loads(json.dumps(obj.as_json())))

    assert hash(obj) == hash(obj2)
    assert obj == obj2
    assert id(obj) != id(obj2)

    obj = ChemicalConstantsPackage.correlations_from_IDs(create_compounds)
    obj2 = PropertyCorrelationsPackage.from_json(json.loads(json.dumps(obj.as_json())))
    assert hash(obj) == hash(obj2)
    assert obj == obj2
    assert id(obj) != id(obj2)

def test_ChemicalConstantsPackage_json_version_exported():
    constants = ChemicalConstantsPackage(MWs=[18.01528, 106.165], names=['water', 'm-xylene'])
    string = json.dumps(constants.as_json())
    c2 = ChemicalConstantsPackage.from_json(json.loads(string))
    assert 'py/object' in string
    assert 'json_version' in string
    assert not hasattr(c2, 'json_version')

def test_ChemicalConstantsPackage_json_export_does_not_change_hashes():
    # There was a nasty bug where the hashing function was changing its result
    # every call
    obj = ChemicalConstantsPackage.correlations_from_IDs(['hexane'])
    hashes_orig = [hash_any_primitive(getattr(obj, k)) for k in obj.correlations]
    copy = obj.as_json()
    hashes_after = [hash_any_primitive(getattr(obj, k)) for k in obj.correlations]
    assert hashes_orig == hashes_after

def test_ChemicalConstantsPackage_json_export_sane_recursion():

    # It might be nice to do something about the duplicate EOSs, but they could be different
    # Really still feels like a different structure for that would be better.
    obj = ChemicalConstantsPackage.correlations_from_IDs(['methane', 'ethane'])
    assert 3 == json.dumps(obj.as_json()).count('VaporPressure')

def test_ChemicalConstantsPackage_json_export_same_output():
    obj = ChemicalConstantsPackage.correlations_from_IDs(['hexane'])
    obj2 = PropertyCorrelationsPackage.from_json(json.loads(json.dumps(obj.as_json())))

    assert hash_any_primitive(obj.constants) == hash_any_primitive(obj2.constants)
    for prop in obj.pure_correlations:
        assert hash_any_primitive(getattr(obj, prop)) ==  hash_any_primitive(getattr(obj2, prop))
    assert hash_any_primitive(obj.VaporPressures) == hash_any_primitive(obj2.VaporPressures)
    assert hash_any_primitive(obj.ViscosityGases) == hash_any_primitive(obj2.ViscosityGases)
    assert hash(obj.SurfaceTensionMixture) == hash(obj2.SurfaceTensionMixture)
    assert hash(obj.VolumeGasMixture) == hash(obj2.VolumeGasMixture)
    for prop in obj.mixture_correlations:
        assert hash_any_primitive(getattr(obj, prop)) ==  hash_any_primitive(getattr(obj2, prop))

    assert hash(obj) == hash(obj2)
    assert obj == obj2

def test_ChemicalConstantsPackage_wrong_behaviors():
    obj = ChemicalConstantsPackage.correlations_from_IDs(['7647-19-0'])
    obj.VolumeLiquids[0].eos is None

def test_lemmon2000_package():
    Ts = (150.0, 200.0, 300.0, 1000.0, 2000.0)
    CoolProp_Cps = [29.030484473246823, 29.03511836728048, 29.103801681330573, 33.046833525551676, 36.210748112152906]
    for T, Cp in zip(Ts, CoolProp_Cps):
        assert_close(Cp, lemmon2000_correlations.HeatCapacityGases[0](T), rtol=2e-7)

def test_compound_index():
    obj = ChemicalConstantsPackage(MWs=[18.01528, 106.165], names=['water', 'm-xylene'],
                             CASs=['7732-18-5', '108-38-3'],
                             InChI_Keys=['XLYOFNOQVPJJNP-UHFFFAOYSA-N', 'IVSZLXZYQVIEFR-UHFFFAOYSA-N'],
                             InChIs=['H2O/h1H2', 'C8H10/c1-7-4-3-5-8(2)6-7/h3-6H,1-2H3'],
                             smiless=['O', 'CC1=CC(=CC=C1)C'], PubChems=[962, 7929],)
    assert 0 == obj.compound_index(name='water')
    assert 1 == obj.compound_index(name='m-xylene')
    assert 1 == obj.compound_index(PubChem=7929)
    assert 0 == obj.compound_index(smiles='O')
    assert 0 == obj.compound_index(CAS='7732-18-5')
    assert 0 == obj.compound_index(InChI='H2O/h1H2')
    assert 1 == obj.compound_index(InChI_Key='IVSZLXZYQVIEFR-UHFFFAOYSA-N')<commit_msg>Add a couple of tests for hashing behavior<commit_after># -*- coding: utf-8 -*-
'''Chemical Engineering Design Library (ChEDL). Utilities for process modeling.
Copyright (C) 2020, Caleb Bell <Caleb.Andrew.Bell@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.'''

import pytest
import thermo
from thermo import *
import chemicals
from chemicals import *
from fluids.numerics import *
from math import *
from chemicals.utils import hash_any_primitive
import json
import os
import numpy as np

@pytest.mark.fuzz
@pytest.mark.slow
def test_ChemicalConstantsPackage_from_json_as_json_large():
    create_compounds = []
    for k in dippr_compounds():
        try:
            if search_chemical(k) is not None:
                create_compounds.append(k)
        except:
            pass

    obj = ChemicalConstantsPackage.constants_from_IDs(create_compounds)
    obj2 = ChemicalConstantsPackage.from_json(json.loads(json.dumps(obj.as_json())))

    assert hash(obj) == hash(obj2)
    assert obj == obj2
    assert id(obj) != id(obj2)

    obj = ChemicalConstantsPackage.correlations_from_IDs(create_compounds)
    obj2 = PropertyCorrelationsPackage.from_json(json.loads(json.dumps(obj.as_json())))
    assert hash(obj) == hash(obj2)
    assert obj == obj2
    assert id(obj) != id(obj2)

    assert obj != int
    assert obj != float

def test_ChemicalConstantsPackage_json_version_exported():
    constants = ChemicalConstantsPackage(MWs=[18.01528, 106.165], names=['water', 'm-xylene'])
    string = json.dumps(constants.as_json())
    c2 = ChemicalConstantsPackage.from_json(json.loads(string))
    assert 'py/object' in string
    assert 'json_version' in string
    assert not hasattr(c2, 'json_version')

def test_ChemicalConstantsPackage_json_export_does_not_change_hashes():
    # There was a nasty bug where the hashing function was changing its result
    # every call
    obj = ChemicalConstantsPackage.correlations_from_IDs(['hexane'])
    hashes_orig = [hash_any_primitive(getattr(obj, k)) for k in obj.correlations]
    copy = obj.as_json()
    hashes_after = [hash_any_primitive(getattr(obj, k)) for k in obj.correlations]
    assert hashes_orig == hashes_after

def test_ChemicalConstantsPackage_json_export_sane_recursion():

    # It might be nice to do something about the duplicate EOSs, but they could be different
    # Really still feels like a different structure for that would be better.
    obj = ChemicalConstantsPackage.correlations_from_IDs(['methane', 'ethane'])
    assert 3 == json.dumps(obj.as_json()).count('VaporPressure')

def test_ChemicalConstantsPackage_json_export_same_output():
    obj = ChemicalConstantsPackage.correlations_from_IDs(['hexane'])
    obj2 = PropertyCorrelationsPackage.from_json(json.loads(json.dumps(obj.as_json())))

    assert hash_any_primitive(obj.constants) == hash_any_primitive(obj2.constants)
    for prop in obj.pure_correlations:
        assert hash_any_primitive(getattr(obj, prop)) ==  hash_any_primitive(getattr(obj2, prop))
    assert hash_any_primitive(obj.VaporPressures) == hash_any_primitive(obj2.VaporPressures)
    assert hash_any_primitive(obj.ViscosityGases) == hash_any_primitive(obj2.ViscosityGases)
    assert hash(obj.SurfaceTensionMixture) == hash(obj2.SurfaceTensionMixture)
    assert hash(obj.VolumeGasMixture) == hash(obj2.VolumeGasMixture)
    for prop in obj.mixture_correlations:
        assert hash_any_primitive(getattr(obj, prop)) ==  hash_any_primitive(getattr(obj2, prop))

    assert hash(obj) == hash(obj2)
    assert obj == obj2

def test_ChemicalConstantsPackage_wrong_behaviors():
    obj = ChemicalConstantsPackage.correlations_from_IDs(['7647-19-0'])
    obj.VolumeLiquids[0].eos is None
    assert obj != int
    assert obj != float

def test_lemmon2000_package():
    Ts = (150.0, 200.0, 300.0, 1000.0, 2000.0)
    CoolProp_Cps = [29.030484473246823, 29.03511836728048, 29.103801681330573, 33.046833525551676, 36.210748112152906]
    for T, Cp in zip(Ts, CoolProp_Cps):
        assert_close(Cp, lemmon2000_correlations.HeatCapacityGases[0](T), rtol=2e-7)

def test_compound_index():
    obj = ChemicalConstantsPackage(MWs=[18.01528, 106.165], names=['water', 'm-xylene'],
                             CASs=['7732-18-5', '108-38-3'],
                             InChI_Keys=['XLYOFNOQVPJJNP-UHFFFAOYSA-N', 'IVSZLXZYQVIEFR-UHFFFAOYSA-N'],
                             InChIs=['H2O/h1H2', 'C8H10/c1-7-4-3-5-8(2)6-7/h3-6H,1-2H3'],
                             smiless=['O', 'CC1=CC(=CC=C1)C'], PubChems=[962, 7929],)
    assert 0 == obj.compound_index(name='water')
    assert 1 == obj.compound_index(name='m-xylene')
    assert 1 == obj.compound_index(PubChem=7929)
    assert 0 == obj.compound_index(smiles='O')
    assert 0 == obj.compound_index(CAS='7732-18-5')
    assert 0 == obj.compound_index(InChI='H2O/h1H2')
    assert 1 == obj.compound_index(InChI_Key='IVSZLXZYQVIEFR-UHFFFAOYSA-N')<|endoftext|>


<commit_before>#!/usr/bin/env python2.7

import logging
import os
import time
import threading
import argparse
from functools import wraps
from os.path import join, getsize, splitext, basename, abspath
from mutagen.easyid3 import EasyID3
from pyechonest import config as en_config
from pyechonest import artist as en_artist

def rate_limited(max_per_second):
    """
    Limits the number of times a function can be called per second.

    Sourced from https://gist.github.com/gregburek/1441055
    """
    lock = threading.Lock()
    min_interval = 1.0 / float(max_per_second)

    def decorate(func):
        last_time_called = [0.0]

        @wraps(func)
        def rate_limited_function(*args, **kwargs):
            lock.acquire()
            elapsed = time.clock() - last_time_called[0]
            left_to_wait = min_interval - elapsed

            if left_to_wait > 0:
                time.sleep(left_to_wait)

            lock.release()

            ret = func(*args, **kwargs)
            last_time_called[0] = time.clock()
            return ret

        return rate_limited_function

    return decorate

class SongInfo(object):
    """
    Holds information about an audio file.
    """

    tag_keys = ('artist', 'author', 'title', 'album', 'genre', 'date', 'performer', 'composer', 'conductor',
             'lyricist', 'arranger', 'tracknumber', 'discnumber', 'discsubtitle', 'language')

    def __init__(self, pathname, **kwargs):
        self.pathname = pathname
        self.__dict__.update(dict.fromkeys(self.tag_keys))
        self.__dict__.update(kwargs)

    def __str__(self):
        return str(self.__dict__)

class ID3Reader(object):
    """
    Reads tag data from ID3 tags.
    """

    def populate(self, info):
        audio = EasyID3(info.pathname)
        for k in SongInfo.tag_keys:
            try:
                setattr(info, k, audio[k])
            except KeyError, e: pass

class ID3Writer(object):
    """
    Writes ID3 tag data.
    """

    def write(self, pathname, info):
        audio = EasyID3(pathname)
        for k in SongInfo.tag_keys:
            value = getattr(info, k)
            if value:
                audio[k] = value
        audio.save()

class EchoNestTerms(object):
    """
    Applies EchoNest term information based on a simple artist search.

    Artist and title information must already be available.
    """

    def populate(self, info):
        if info.artist and not info.genre:
            self._populate(info)

    @rate_limited(1.5)
    def _populate(self, info):
        artist = en_artist.Artist(info.artist[0])
        genre = []
        for term in artist.get_terms(sort='weight', cache=True):
            if term['weight'] >= 0.5:
                genre.append(term['name'].title())
        info.genre = genre

class TagUpdater(object):
    """
    Updates tags in audio files.
    """

    def __init__(self):
        self.populators = []
        self.writers = []
        self.dry_run = False

    def tag_dir(self, path, accepted_exts):
        """
        Tag all the files within a directory.
        """
        for root, dirs, files in os.walk(path):
            for f in files:
                name, ext = splitext(f)
                if ext.lower() in accepted_exts:
                    try:
                        self.tag(join(root, f))
                    except Exception, e:
                        logging.warning("Failed to tag {}".format(abspath(join(root, f))), exc_info=True)

    def tag(self, pathname):
        """
        Updates tags within a specific file.
        """
        info = SongInfo(pathname)
        self.populate(info)
        logging.info("Tagging {} (artist: {}, title: {}, album: {}, genre: {})".format(abspath(pathname), info.artist, info.title, info.album, info.genre))
        if not self.dry_run:
            for w in self.writers:
                w.write(pathname, info)

    def populate(self, info):
        for p in self.populators:
            p.populate(info)

if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG, format="* %(message)s")

    parser = argparse.ArgumentParser(description='Update tags in audio files')
    parser.add_argument('--dry-run', action='store_true', help='don\'t change files')
    parser.add_argument('--echonest-key', required=True, help='your EchoNest API key')
    parser.add_argument('dir', help='the directory to scan')

    args = parser.parse_args()

    u = TagUpdater()
    if args.dry_run:
        u.dry_run = True

    u.populators.append(ID3Reader()) # read artist and title from ID3 data
    u.populators.append(EchoNestTerms()) # fetch terms and add as genre

    u.writers.append(ID3Writer()) # write ID3 data

    en_config.ECHO_NEST_API_KEY = args.echonest_key
    u.tag_dir(args.dir, ('.mp3',))<commit_msg>Make tagdog.py executable.<commit_after>#!/usr/bin/env python2.7

import logging
import os
import time
import threading
import argparse
from functools import wraps
from os.path import join, getsize, splitext, basename, abspath
from mutagen.easyid3 import EasyID3
from pyechonest import config as en_config
from pyechonest import artist as en_artist

def rate_limited(max_per_second):
    """
    Limits the number of times a function can be called per second.

    Sourced from https://gist.github.com/gregburek/1441055
    """
    lock = threading.Lock()
    min_interval = 1.0 / float(max_per_second)

    def decorate(func):
        last_time_called = [0.0]

        @wraps(func)
        def rate_limited_function(*args, **kwargs):
            lock.acquire()
            elapsed = time.clock() - last_time_called[0]
            left_to_wait = min_interval - elapsed

            if left_to_wait > 0:
                time.sleep(left_to_wait)

            lock.release()

            ret = func(*args, **kwargs)
            last_time_called[0] = time.clock()
            return ret

        return rate_limited_function

    return decorate

class SongInfo(object):
    """
    Holds information about an audio file.
    """

    tag_keys = ('artist', 'author', 'title', 'album', 'genre', 'date', 'performer', 'composer', 'conductor',
             'lyricist', 'arranger', 'tracknumber', 'discnumber', 'discsubtitle', 'language')

    def __init__(self, pathname, **kwargs):
        self.pathname = pathname
        self.__dict__.update(dict.fromkeys(self.tag_keys))
        self.__dict__.update(kwargs)

    def __str__(self):
        return str(self.__dict__)

class ID3Reader(object):
    """
    Reads tag data from ID3 tags.
    """

    def populate(self, info):
        audio = EasyID3(info.pathname)
        for k in SongInfo.tag_keys:
            try:
                setattr(info, k, audio[k])
            except KeyError, e: pass

class ID3Writer(object):
    """
    Writes ID3 tag data.
    """

    def write(self, pathname, info):
        audio = EasyID3(pathname)
        for k in SongInfo.tag_keys:
            value = getattr(info, k)
            if value:
                audio[k] = value
        audio.save()

class EchoNestTerms(object):
    """
    Applies EchoNest term information based on a simple artist search.

    Artist and title information must already be available.
    """

    def populate(self, info):
        if info.artist and not info.genre:
            self._populate(info)

    @rate_limited(1.5)
    def _populate(self, info):
        artist = en_artist.Artist(info.artist[0])
        genre = []
        for term in artist.get_terms(sort='weight', cache=True):
            if term['weight'] >= 0.5:
                genre.append(term['name'].title())
        info.genre = genre

class TagUpdater(object):
    """
    Updates tags in audio files.
    """

    def __init__(self):
        self.populators = []
        self.writers = []
        self.dry_run = False

    def tag_dir(self, path, accepted_exts):
        """
        Tag all the files within a directory.
        """
        for root, dirs, files in os.walk(path):
            for f in files:
                name, ext = splitext(f)
                if ext.lower() in accepted_exts:
                    try:
                        self.tag(join(root, f))
                    except Exception, e:
                        logging.warning("Failed to tag {}".format(abspath(join(root, f))), exc_info=True)

    def tag(self, pathname):
        """
        Updates tags within a specific file.
        """
        info = SongInfo(pathname)
        self.populate(info)
        logging.info("Tagging {} (artist: {}, title: {}, album: {}, genre: {})".format(abspath(pathname), info.artist, info.title, info.album, info.genre))
        if not self.dry_run:
            for w in self.writers:
                w.write(pathname, info)

    def populate(self, info):
        for p in self.populators:
            p.populate(info)

if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG, format="* %(message)s")

    parser = argparse.ArgumentParser(description='Update tags in audio files')
    parser.add_argument('--dry-run', action='store_true', help='don\'t change files')
    parser.add_argument('--echonest-key', required=True, help='your EchoNest API key')
    parser.add_argument('dir', help='the directory to scan')

    args = parser.parse_args()

    u = TagUpdater()
    if args.dry_run:
        u.dry_run = True

    u.populators.append(ID3Reader()) # read artist and title from ID3 data
    u.populators.append(EchoNestTerms()) # fetch terms and add as genre

    u.writers.append(ID3Writer()) # write ID3 data

    en_config.ECHO_NEST_API_KEY = args.echonest_key
    u.tag_dir(args.dir, ('.mp3',))<|endoftext|>