<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
import datetime
import click
import logging
from .helpers import render_bot_statistic, render_bot_tradelog, render_bot_title, render_signal_details

log = logging.getLogger(__name__)

BUY = 1
SELL = -1
WAIT = 0
QUIT = -99
signal_map = {
    BUY: "BUY",
    WAIT: "WAIT",
    SELL: "SELL",
    QUIT: "QUIT"
}
# Signals for strategies.

class Signal(object):

    def __init__(self, signal, date):
        self.value = signal
        self.date = date

class Strategy(object):

    """Docstring for Strategy. """

    def __str__(self):
        return "{}".format(self.__class__)

    def __init__(self):
        self._signal_history = []
        """Store last emitted signals"""
        self._bot = None
        self._value = None
        """Current closing value of the market"""
        self._date = None
        """Current date of of the value. Needed for signal generation"""
        self._details = {}
        """Dictionary with details on the signal(s)
        {"indicator": {"signal": 1, "details": Foo}}
        """

    def details(self, market, resolution):
        """Will return details on the reasong why the signal was emited."""
        return self._details

    def set_bot(self, bot):
        self._bot = bot

    def signal(self, market, resolution, start, end):
        """Will return either a BUY, SELL or WAIT signal for the given
        market"""
        raise NotImplementedError

    def sma(self, chart, window=12):
        """Generates a trade signal based on a moving averanges. A BUY
        signal is generated if the EMA higher than the closing price. A
        SELL signal is emited if the EMA is lower than the closing
        price."""

        sma = chart.sma(window)[-1]
        signal = WAIT

        if self._value > sma:
            signal = BUY
        elif self._value < sma:
            signal = SELL
        self._details["SMA"] = {"signal": signal, "details": "SMA{}: {})".format(window, sma)}
        return Signal(signal, self._date)

    def ema(self, chart, window=12):
        """Generates a trade signal based on a moving averanges. A BUY
        signal is generated if the EMA higher than the closing price. A
        SELL signal is emited if the EMA is lower than the closing
        price."""

        ema = chart.ema(window)[-1]
        signal = WAIT

        if self._value > ema:
            signal = BUY
        elif self._value < ema:
            signal = SELL
        self._details["EMA"] = {"signal": signal, "details": "EMA{}: {})".format(window, ema)}
        return Signal(signal, self._date)

    def double_cross(self, chart, fast=12, slow=26):
        """Generates a trade signal based on two moving averanges with
        different width. A BUY signal is generated if the faster EMA
        crosses the slower EMA in up direction and the faster EMA is higher
        than the closing price. A SELL signal is emited if the faster SMA
        crosses the lower from above and is lower than the closing
        price."""

        ema_1 = chart.ema(fast)[-1]
        ema_2 = chart.ema(slow)[-1]
        signal = WAIT

        if self._value > ema_1 and ema_1 > ema_2:
            signal = BUY
        elif self._value < ema_1 and ema_1 < ema_2:
            signal = SELL
        self._details["EMA"] = {"signal": signal, "details": "EMA{}: {}, EMA{}: {})".format(fast, ema_1, slow, ema_2)}
        return Signal(signal, self._date)

    def macdh(self, chart):
        """Generates a SELL signal as soon as the macdh value changes
        from positve value into negativ value. It generates a BUY signal
        if the value from negativ to positiv."""

        macdh = chart.macdh()[::-1][0:2]
        if macdh[0] < 0 and macdh[1] > 0:
            signal = SELL
        elif macdh[0] > 0 and macdh[1] < 0:
            signal = BUY
        else:
            signal = WAIT
        self._details["MACDH"] = {"signal": signal, "details": "MACDH: {}".format(macdh)}
        return Signal(signal, self._date)

class InteractivStrategyWrapper(object):

    def __init__(self, strategie):
        self._strategie = strategie
        self._bot = None

    def set_bot(self, bot):
        self._bot = bot

    def __str__(self):
        return "Interavtiv: {}".format(self._strategie)

    def signal(self, market, resolution, start, end):
        """Will return either a BUY, SELL or WAIT signal for the given
        market"""

        # Get current chart
        click.echo(render_bot_title(self._bot, market, resolution))
        signal = self._strategie.signal(market, resolution, start, end)

        click.echo('Signal: {} {}'.format(signal.date, signal_map[signal.value]))
        click.echo(render_signal_details(self._strategie.details(market, resolution)))
        click.echo('')
        options = []
        if self._bot.btc:
            options.append("b) Buy")
        if self._bot.amount:
            options.append("s) Sell")
        options.append("l) Tradelog")
        options.append("p) Performance of bot")
        options.append("d) Show details on signal")
        options.append("q) Quit")
        options.append("")
        options.append("Press any key to continue")

        click.echo(u'\n'.join(options))
        c = click.getchar()
        if c == 'b' and self._bot.btc:
            # btc = click.prompt('BTC', default=self._bot.btc)
            if click.confirm('Buy for {} btc?'.format(self._bot.btc)):
                return Signal(BUY, datetime.datetime.utcnow())
        if c == 's' and self._bot.amount:
            # amount = click.prompt('Amount', default=self._bot.amount)
            if click.confirm('Sell {}?'.format(self._bot.amount)):
                return Signal(SELL, datetime.datetime.utcnow())
        if c == 'l':
            click.echo(render_bot_tradelog(self._bot.trades))
        if c == 'p':
            click.echo(render_bot_statistic(self._bot.stat()))
        if c == 'd':
            click.echo(render_signal_details(self._strategie.details(market, resolution)))
        if c == 'q':
            return Signal(QUIT, datetime.datetime.utcnow())
        else:
            return Signal(WAIT, datetime.datetime.utcnow())
<commit_msg>Removed references to self in indicator method of the strategy<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
import datetime
import click
import logging
from .helpers import render_bot_statistic, render_bot_tradelog, render_bot_title, render_signal_details

log = logging.getLogger(__name__)

BUY = 1
SELL = -1
WAIT = 0
QUIT = -99
signal_map = {
    BUY: "BUY",
    WAIT: "WAIT",
    SELL: "SELL",
    QUIT: "QUIT"
}
# Signals for strategies.

class Signal(object):

    def __init__(self, signal, date):
        self.value = signal
        self.date = date

class Strategy(object):

    """Docstring for Strategy. """

    def __str__(self):
        return "{}".format(self.__class__)

    def __init__(self):
        self._signal_history = []
        """Store last emitted signals"""
        self._bot = None
        self._value = None
        """Current closing value of the market"""
        self._date = None
        """Current date of of the value. Needed for signal generation"""
        self._chart = None
        """Current chart"""
        self._details = {}
        """Dictionary with details on the signal(s)
        {"indicator": {"signal": 1, "details": Foo}}
        """

    def details(self, market, resolution):
        """Will return details on the reasong why the signal was emited."""
        return self._details

    def set_bot(self, bot):
        self._bot = bot

    def signal(self, market, resolution, start, end):
        """Will return either a BUY, SELL or WAIT signal for the given
        market"""
        raise NotImplementedError

    def sma(self, chart, window=12):
        """Generates a trade signal based on a moving averanges. A BUY
        signal is generated if the EMA higher than the closing price. A
        SELL signal is emited if the EMA is lower than the closing
        price."""

        sma = chart.sma(window)[-1]
        closing = chart.values()
        value = closing[-1][1]
        date = datetime.datetime.utcfromtimestamp(closing[-1][0])

        signal = WAIT
        if value > sma:
            signal = BUY
        elif value < sma:
            signal = SELL
        self._details["SMA"] = {"signal": signal, "details": "SMA{}: {})".format(window, sma)}
        return Signal(signal, date)

    def ema(self, chart, window=12):
        """Generates a trade signal based on a moving averanges. A BUY
        signal is generated if the EMA higher than the closing price. A
        SELL signal is emited if the EMA is lower than the closing
        price."""

        ema = chart.ema(window)[-1]
        closing = chart.values()
        value = closing[-1][1]
        date = datetime.datetime.utcfromtimestamp(closing[-1][0])

        signal = WAIT
        if value > ema:
            signal = BUY
        elif value < ema:
            signal = SELL
        self._details["EMA"] = {"signal": signal, "details": "EMA{}: {})".format(window, ema)}
        return Signal(signal, date)

    def double_cross(self, chart, fast=12, slow=26):
        """Generates a trade signal based on two moving averanges with
        different width. A BUY signal is generated if the faster EMA
        crosses the slower EMA in up direction and the faster EMA is higher
        than the closing price. A SELL signal is emited if the faster SMA
        crosses the lower from above and is lower than the closing
        price."""

        closing = chart.values()
        value = closing[-1][1]
        date = datetime.datetime.utcfromtimestamp(closing[-1][0])
        ema_1 = chart.ema(fast)[-1]
        ema_2 = chart.ema(slow)[-1]
        signal = WAIT

        if value > ema_1 and ema_1 > ema_2:
            signal = BUY
        elif value < ema_1 and ema_1 < ema_2:
            signal = SELL
        self._details["EMA"] = {"signal": signal, "details": "EMA{}: {}, EMA{}: {})".format(fast, ema_1, slow, ema_2)}
        return Signal(signal, date)

    def macdh(self, chart):
        """Generates a SELL signal as soon as the macdh value changes
        from positve value into negativ value. It generates a BUY signal
        if the value from negativ to positiv."""

        macdh = chart.macdh()[::-1][0:2]
        closing = chart.values()
        date = datetime.datetime.utcfromtimestamp(closing[-1][0])
        if macdh[0] < 0 and macdh[1] > 0:
            signal = SELL
        elif macdh[0] > 0 and macdh[1] < 0:
            signal = BUY
        else:
            signal = WAIT
        self._details["MACDH"] = {"signal": signal, "details": "MACDH: {}".format(macdh)}
        return Signal(signal, date)

class InteractivStrategyWrapper(object):

    def __init__(self, strategie):
        self._strategie = strategie
        self._bot = None

    def set_bot(self, bot):
        self._bot = bot

    def __str__(self):
        return "Interavtiv: {}".format(self._strategie)

    def signal(self, market, resolution, start, end):
        """Will return either a BUY, SELL or WAIT signal for the given
        market"""

        # Get current chart
        click.echo(render_bot_title(self._bot, market, resolution))
        signal = self._strategie.signal(market, resolution, start, end)

        click.echo('Signal: {} {}'.format(signal.date, signal_map[signal.value]))
        click.echo(render_signal_details(self._strategie.details(market, resolution)))
        click.echo('')
        options = []
        if self._bot.btc:
            options.append("b) Buy")
        if self._bot.amount:
            options.append("s) Sell")
        options.append("l) Tradelog")
        options.append("p) Performance of bot")
        options.append("d) Show details on signal")
        options.append("q) Quit")
        options.append("")
        options.append("Press any key to continue")

        click.echo(u'\n'.join(options))
        c = click.getchar()
        if c == 'b' and self._bot.btc:
            # btc = click.prompt('BTC', default=self._bot.btc)
            if click.confirm('Buy for {} btc?'.format(self._bot.btc)):
                return Signal(BUY, datetime.datetime.utcnow())
        if c == 's' and self._bot.amount:
            # amount = click.prompt('Amount', default=self._bot.amount)
            if click.confirm('Sell {}?'.format(self._bot.amount)):
                return Signal(SELL, datetime.datetime.utcnow())
        if c == 'l':
            click.echo(render_bot_tradelog(self._bot.trades))
        if c == 'p':
            click.echo(render_bot_statistic(self._bot.stat()))
        if c == 'd':
            click.echo(render_signal_details(self._strategie.details(market, resolution)))
        if c == 'q':
            return Signal(QUIT, datetime.datetime.utcnow())
        else:
            return Signal(WAIT, datetime.datetime.utcnow())
<|endoftext|>


<commit_before>import datetime

from django.conf import settings

from django.core.exceptions import ObjectDoesNotExist

from .models import Lock, StudentsA, StudentsB, \
    Stumodules, TimetableA, TimetableB, \
    WeekmapnumericA, WeekmapnumericB, \
    WeekstructureA, WeekstructureB, \
    LecturerA, LecturerB, \
    RoomsA, RoomsB, \
    SitesA, SitesB, \
    ModuleA, ModuleB

_SETID = settings.ROOMBOOKINGS_SETID

_week_map = {}
_week_num_date_map = {}

_session_type_map = {
    "L": "Lecture",
    "PBL": "Problem Based Learning",
    "P": "Practical"
}

_rooms_cache = {}

def _get_cache(model_name):
    models = {
        "module": [ModuleA, ModuleB],
        "students": [StudentsA, StudentsB],
        "timetable": [TimetableA, TimetableB],
        "weekmapnumeric": [WeekmapnumericA, WeekmapnumericB],
        "weekstructure": [WeekstructureA, WeekstructureB],
        "lecturer": [LecturerA, LecturerB],
        "rooms": [RoomsA, RoomsB],
        "sites": [SitesA, SitesB]
    }
    lock = Lock.objects.all()[0]
    model = models[model_name][0] if lock.a else models[model_name][1]
    return model

def _get_student_by_upi(upi):
    print("Getting student by UPI: " + upi)
    students = _get_cache("students")
    # Assume the current Set ID due to caching
    upi_upper = upi.upper()
    student = students.objects.filter(
        qtype2=upi_upper
    )[0]
    print("Got student")
    return student

def _get_student_modules(student):
    print("Getting student modules for student: " + student.qtype2)
    raw_query = 'SELECT * FROM CMIS_OWNER.STUMODULES WHERE SETID=\'LIVE-17-18\' AND studentid=\'{}\''.format(
        student.studentid
    )
    student_modules = list(Stumodules.objects.raw(raw_query))
    print("Got student modules for student: " + student.qtype2)
    return student_modules

def _get_lecturer_details(lecturer_upi):
    lecturers = _get_cache("lecturer")
    details = {
        "name": "unknown",
        "email": "unknown"
    }
    try:
        lecturer = lecturers.objects.get(lecturerid=lecturer_upi)
        details["name"] = lecturer.name
        details["email"] = lecturer.linkcode + "@ucl.ac.uk"
    except ObjectDoesNotExist:
        pass

    return details

def _get_timetable_events(student_modules):
    print("Getting timetabled events")
    if not _week_map:
        _map_weeks()

    timetable = _get_cache("timetable")
    student_timetable = {}
    for module in student_modules:
        print("Getting data for Module ID " + module.moduleid)
        events_data = timetable.objects.filter(
            moduleid=module.moduleid,
            modgrpcode=module.modgrpcode
        )
        for event in events_data:
            for date in _get_real_dates(event):
                date_str = date.strftime("%Y-%m-%d")
                event_data = {
                    "start_time": event.starttime,
                    "end_time": event.finishtime,
                    "duration": event.duration,
                    "module": {
                        "module_code": event.linkcode,
                        "module_id": event.moduleid,
                        "course_owner": event.owner,
                        "lecturer": _get_lecturer_details(event.lecturerid),
                    },
                    "location": _get_location_details(event.siteid, event.roomid),
                    "session_type": event.moduletype,
                    "session_type_str": _get_session_type_str(event.moduletype),
                    "session_group": module.modgrpcode
                }
                if date_str not in student_timetable:
                    student_timetable[date_str] = []
                student_timetable[date_str].append(event_data)
    print("Got timetabled events")
    return student_timetable

def _get_timetable_events_module_list(module_list):
    if not _week_map:
        _map_weeks()

    timetable = _get_cache("timetable")
    modules = _get_cache("module")

    full_modules = []

    for module in module_list:
        try:
            full_modules.append(modules.objects.get(moduleid=module))
        except ObjectDoesNotExist:
            return False

    returned_timetable = {}
    for module in full_modules:
        events_data = timetable.objects.filter(
            moduleid=module
        )
        for event in events_data:
            print(event)
            for date in _get_real_dates(event):
                date_str = date.strftime("%Y-%m-%d")
                event_data = {
                    "start_time": event.starttime,
                    "end_time": event.finishtime,
                    "duration": event.duration,
                    "module": {
                        "module_code": event.linkcode,
                        "module_id": event.moduleid,
                        "course_owner": event.owner,
                        "lecturer": _get_lecturer_details(event.lecturerid),
                    },
                    "location": _get_location_details(event.siteid, event.roomid),
                    "session_type": event.moduletype,
                    "session_type_str": _get_session_type_str(event.moduletype),
                    "session_group": module.modgrpcode
                }
                if date_str not in returned_timetable:
                    returned_timetable[date_str] = []
                returned_timetable[date_str].append(event_data)
    return returned_timetable

def _map_weeks():
    print("Mapping weeks")
    weekmapnumeric = _get_cache("weekmapnumeric")
    weekstructure = _get_cache("weekstructure")
    week_nums = weekmapnumeric.objects.all()
    week_strs = weekstructure.objects.all()

    for week in week_strs:
        _week_num_date_map[week.weeknumber] = week.startdate

    for week in week_nums:
        if week.weekid not in _week_map:
            _week_map[week.weekid] = []
        _week_map[week.weekid].append(week.weeknumber)
    print("Weeks mapped successfully")

def _get_real_dates(slot):
    return [
        _week_num_date_map[startdate] + datetime.timedelta(
            days=slot.weekday - 1
        )
        for startdate in _week_map[slot.weekid]
    ]

def _get_session_type_str(session_type):
    if session_type in _session_type_map:
        return _session_type_map[session_type]
    else:
        return "Unknown"

def _get_location_details(siteid, roomid):
    if not roomid:
        return {}
    if not siteid:
        return {}

    cache_id = siteid + "___" + roomid
    if cache_id not in _rooms_cache:
        rooms = _get_cache("rooms")
        sites = _get_cache("sites")
        try:
            room = rooms.objects.filter(roomid=roomid, siteid=siteid)[0]
            site = sites.objects.filter(siteid=siteid)[0]
        except IndexError:
            return {}
        _rooms_cache[cache_id] = {
            "name": room.name,
            "capacity": room.capacity,
            "type": room.type,
            "address": [
                site.address1,
                site.address2,
                site.address3
            ],
            "site_name": site.sitename
        }
    return _rooms_cache[cache_id]

def get_student_timetable(upi, date_filter=None):
    print("*** GETTING STUDENT TIMETABLE FOR UPI " + upi + " ***")
    student = _get_student_by_upi(upi)
    print("Getting modules....")
    student_modules = _get_student_modules(student)
    print("Getting events...")
    student_events = _get_timetable_events(student_modules)
    print("Returning events...")
    if date_filter:
        if date_filter in student_events:
            filtered_student_events = {
                date_filter: student_events[date_filter]
            }
        else:
            filtered_student_events = {
                date_filter: []
            }
        return filtered_student_events
    return student_events

def get_custom_timetable(modules, date_filter=None):
    events = _get_timetable_events_module_list(modules)
    if events:
        if date_filter:
            if date_filter in events:
                filtered_events = {
                    date_filter: events[date_filter]
                }
            else:
                filtered_events = {
                    date_filter: []
                }
            return filtered_events
        return events
    return None
<commit_msg>Adds date debug message<commit_after>import datetime

from django.conf import settings

from django.core.exceptions import ObjectDoesNotExist

from .models import Lock, StudentsA, StudentsB, \
    Stumodules, TimetableA, TimetableB, \
    WeekmapnumericA, WeekmapnumericB, \
    WeekstructureA, WeekstructureB, \
    LecturerA, LecturerB, \
    RoomsA, RoomsB, \
    SitesA, SitesB, \
    ModuleA, ModuleB

_SETID = settings.ROOMBOOKINGS_SETID

_week_map = {}
_week_num_date_map = {}

_session_type_map = {
    "L": "Lecture",
    "PBL": "Problem Based Learning",
    "P": "Practical"
}

_rooms_cache = {}

def _get_cache(model_name):
    models = {
        "module": [ModuleA, ModuleB],
        "students": [StudentsA, StudentsB],
        "timetable": [TimetableA, TimetableB],
        "weekmapnumeric": [WeekmapnumericA, WeekmapnumericB],
        "weekstructure": [WeekstructureA, WeekstructureB],
        "lecturer": [LecturerA, LecturerB],
        "rooms": [RoomsA, RoomsB],
        "sites": [SitesA, SitesB]
    }
    lock = Lock.objects.all()[0]
    model = models[model_name][0] if lock.a else models[model_name][1]
    return model

def _get_student_by_upi(upi):
    print("Getting student by UPI: " + upi)
    students = _get_cache("students")
    # Assume the current Set ID due to caching
    upi_upper = upi.upper()
    student = students.objects.filter(
        qtype2=upi_upper
    )[0]
    print("Got student")
    return student

def _get_student_modules(student):
    print("Getting student modules for student: " + student.qtype2)
    raw_query = 'SELECT * FROM CMIS_OWNER.STUMODULES WHERE SETID=\'LIVE-17-18\' AND studentid=\'{}\''.format(
        student.studentid
    )
    student_modules = list(Stumodules.objects.raw(raw_query))
    print("Got student modules for student: " + student.qtype2)
    return student_modules

def _get_lecturer_details(lecturer_upi):
    lecturers = _get_cache("lecturer")
    details = {
        "name": "unknown",
        "email": "unknown"
    }
    try:
        lecturer = lecturers.objects.get(lecturerid=lecturer_upi)
        details["name"] = lecturer.name
        details["email"] = lecturer.linkcode + "@ucl.ac.uk"
    except ObjectDoesNotExist:
        pass

    return details

def _get_timetable_events(student_modules):
    print("Getting timetabled events")
    if not _week_map:
        _map_weeks()

    timetable = _get_cache("timetable")
    student_timetable = {}
    for module in student_modules:
        print("Getting data for Module ID " + module.moduleid)
        events_data = timetable.objects.filter(
            moduleid=module.moduleid,
            modgrpcode=module.modgrpcode
        )
        for event in events_data:
            for date in _get_real_dates(event):
                date_str = date.strftime("%Y-%m-%d")
                event_data = {
                    "start_time": event.starttime,
                    "end_time": event.finishtime,
                    "duration": event.duration,
                    "module": {
                        "module_code": event.linkcode,
                        "module_id": event.moduleid,
                        "course_owner": event.owner,
                        "lecturer": _get_lecturer_details(event.lecturerid),
                    },
                    "location": _get_location_details(event.siteid, event.roomid),
                    "session_type": event.moduletype,
                    "session_type_str": _get_session_type_str(event.moduletype),
                    "session_group": module.modgrpcode
                }
                if date_str not in student_timetable:
                    student_timetable[date_str] = []
                student_timetable[date_str].append(event_data)
    print("Got timetabled events")
    return student_timetable

def _get_timetable_events_module_list(module_list):
    if not _week_map:
        _map_weeks()

    timetable = _get_cache("timetable")
    modules = _get_cache("module")

    full_modules = []

    for module in module_list:
        try:
            full_modules.append(modules.objects.get(moduleid=module))
        except ObjectDoesNotExist:
            return False

    returned_timetable = {}
    for module in full_modules:
        events_data = timetable.objects.filter(
            moduleid=module
        )
        for event in events_data:
            print(event)
            for date in _get_real_dates(event):
                print(date)
                date_str = date.strftime("%Y-%m-%d")
                event_data = {
                    "start_time": event.starttime,
                    "end_time": event.finishtime,
                    "duration": event.duration,
                    "module": {
                        "module_code": event.linkcode,
                        "module_id": event.moduleid,
                        "course_owner": event.owner,
                        "lecturer": _get_lecturer_details(event.lecturerid),
                    },
                    "location": _get_location_details(event.siteid, event.roomid),
                    "session_type": event.moduletype,
                    "session_type_str": _get_session_type_str(event.moduletype),
                    "session_group": module.modgrpcode
                }
                if date_str not in returned_timetable:
                    returned_timetable[date_str] = []
                returned_timetable[date_str].append(event_data)
    return returned_timetable

def _map_weeks():
    print("Mapping weeks")
    weekmapnumeric = _get_cache("weekmapnumeric")
    weekstructure = _get_cache("weekstructure")
    week_nums = weekmapnumeric.objects.all()
    week_strs = weekstructure.objects.all()

    for week in week_strs:
        _week_num_date_map[week.weeknumber] = week.startdate

    for week in week_nums:
        if week.weekid not in _week_map:
            _week_map[week.weekid] = []
        _week_map[week.weekid].append(week.weeknumber)
    print("Weeks mapped successfully")

def _get_real_dates(slot):
    return [
        _week_num_date_map[startdate] + datetime.timedelta(
            days=slot.weekday - 1
        )
        for startdate in _week_map[slot.weekid]
    ]

def _get_session_type_str(session_type):
    if session_type in _session_type_map:
        return _session_type_map[session_type]
    else:
        return "Unknown"

def _get_location_details(siteid, roomid):
    if not roomid:
        return {}
    if not siteid:
        return {}

    cache_id = siteid + "___" + roomid
    if cache_id not in _rooms_cache:
        rooms = _get_cache("rooms")
        sites = _get_cache("sites")
        try:
            room = rooms.objects.filter(roomid=roomid, siteid=siteid)[0]
            site = sites.objects.filter(siteid=siteid)[0]
        except IndexError:
            return {}
        _rooms_cache[cache_id] = {
            "name": room.name,
            "capacity": room.capacity,
            "type": room.type,
            "address": [
                site.address1,
                site.address2,
                site.address3
            ],
            "site_name": site.sitename
        }
    return _rooms_cache[cache_id]

def get_student_timetable(upi, date_filter=None):
    print("*** GETTING STUDENT TIMETABLE FOR UPI " + upi + " ***")
    student = _get_student_by_upi(upi)
    print("Getting modules....")
    student_modules = _get_student_modules(student)
    print("Getting events...")
    student_events = _get_timetable_events(student_modules)
    print("Returning events...")
    if date_filter:
        if date_filter in student_events:
            filtered_student_events = {
                date_filter: student_events[date_filter]
            }
        else:
            filtered_student_events = {
                date_filter: []
            }
        return filtered_student_events
    return student_events

def get_custom_timetable(modules, date_filter=None):
    events = _get_timetable_events_module_list(modules)
    if events:
        if date_filter:
            if date_filter in events:
                filtered_events = {
                    date_filter: events[date_filter]
                }
            else:
                filtered_events = {
                    date_filter: []
                }
            return filtered_events
        return events
    return None
<|endoftext|>


<commit_before>from blessings import Terminal
from django.core.management.base import BaseCommand
from sphinx.ext.intersphinx import fetch_inventory, INVENTORY_FILENAME

from cbv.models import Klass

t = Terminal()

class Command(BaseCommand):
    args = ''
    help = 'Fetches the docs urls for CBV Classes.'
    django_doc_url = 'http://django.readthedocs.org/en/{version}'
    # these versions need to be obtained manually from RTFD website
    # do not ask me why it is `1.5.x` for Django 1.5..
    django_versions = ['1.3', '1.4', '1.5.x']

    def bless_prints(self, version, msg):
        # wish the blessings lib supports method chaining..
        a = t.blue('Django ' + version + ': ')
        z = t.green(msg)
        print a + z

    def handle(self, *args, **options):
        """
        Docs urls for Classes can differ between Django versions.
        This script sets correct urls for specific Classes using bits from
        `sphinx.ext.intersphinx` to fetch docs inventory data.
        """

        for v in self.django_versions:
            cnt = 1

            ver_url = self.django_doc_url.format(version=v)
            ver_inv_url = ver_url + '/' + INVENTORY_FILENAME

            if '.x' in v:
                v = v.split('.x')[0]
            # get flat list of CBV classes per Django version
            qs_lookups = {'module__project_version__version_number': v}
            ver_classes = Klass.objects.filter(**qs_lookups).values_list(
                'name', flat=True)
            self.bless_prints(v, 'Found {0} classes'.format(len(ver_classes)))
            self.bless_prints(v, 'Getting inventory @ {0}'.format(ver_inv_url))
            # fetch some inventory dataz
            # the arg `None` should be a Sphinx instance object..
            invdata = fetch_inventory(None, ver_url, ver_inv_url)
            # we only want classes..
            for item in invdata[u'py:class']:
                # ..which come from django.views
                if 'django.views.' in item:
                    # get class name
                    inv_klass = item.split('.')[-1]
                    # save hits to db and update only required classes
                    if inv_klass in ver_classes:
                        url = invdata[u'py:class'][item][2]
                        qs_lookups.update({
                            'name': inv_klass
                        })
                        Klass.objects.filter(**qs_lookups).update(docs_url=url)
                        cnt += 1
            self.bless_prints(v, 'Updated {0} classes\n'.format(cnt))
<commit_msg>Use inventory files directly from Django<commit_after>from blessings import Terminal
from django.core.management.base import BaseCommand
from sphinx.ext.intersphinx import fetch_inventory

from cbv.models import Klass

t = Terminal()

class Command(BaseCommand):
    args = ''
    help = 'Fetches the docs urls for CBV Classes.'
    django_doc_url = 'http://docs.djangoproject.org/en/{version}'
    # versions of Django which are supported by CCBV
    django_versions = ['1.3', '1.4', '1.5']
    # Django has custom inventory file name
    inv_filename = '_objects'

    def bless_prints(self, version, msg):
        # wish the blessings lib supports method chaining..
        a = t.blue('Django ' + version + ': ')
        z = t.green(msg)
        print a + z

    def handle(self, *args, **options):
        """
        Docs urls for Classes can differ between Django versions.
        This script sets correct urls for specific Classes using bits from
        `sphinx.ext.intersphinx` to fetch docs inventory data.
        """

        for v in self.django_versions:
            cnt = 1

            ver_url = self.django_doc_url.format(version=v)
            ver_inv_url = ver_url + '/' + self.inv_filename

            # get flat list of CBV classes per Django version
            qs_lookups = {'module__project_version__version_number': v}
            ver_classes = Klass.objects.filter(**qs_lookups).values_list(
                'name', flat=True)
            self.bless_prints(v, 'Found {0} classes'.format(len(ver_classes)))
            self.bless_prints(v, 'Getting inventory @ {0}'.format(ver_inv_url))
            # fetch some inventory dataz
            # the arg `None` should be a Sphinx instance object..
            invdata = fetch_inventory(None, ver_url, ver_inv_url)
            # we only want classes..
            for item in invdata[u'py:class']:
                # ..which come from django.views
                if 'django.views.' in item:
                    # get class name
                    inv_klass = item.split('.')[-1]
                    # save hits to db and update only required classes
                    if inv_klass in ver_classes:
                        url = invdata[u'py:class'][item][2]
                        qs_lookups.update({
                            'name': inv_klass
                        })
                        Klass.objects.filter(**qs_lookups).update(docs_url=url)
                        cnt += 1
            self.bless_prints(v, 'Updated {0} classes\n'.format(cnt))
<|endoftext|>


<commit_before>import datetime
from decimal import Decimal, InvalidOperation
from django.forms.util import ErrorList
import types
import re

from rest_framework import serializers
from dateutil.parser import parse

from legalaid.utils.csvupload.constants import AGE_RANGE, POSTCODE_RE, \
    ELIGIBILITY_CODES, DISABILITY_INDICATOR, EXEMPTION_CODES, \
    SERVICE_ADAPTATIONS, ADVICE_TYPES, VALID_OUTCOMES, VALID_STAGE_REACHED, \
    VALID_MATTER_TYPE1, VALID_MATTER_TYPE2, DETERMINATION_CODES, \
    PREFIX_CATEGORY_LOOKUP, STAGE_REACHED_NOT_ALLOWED_MT1S, STAGE_REACHED_REQUIRED_MT1S

def validate_decimal(val):
    if val:
        try:
            val = val.replace(u',', u'')
            val = Decimal(val)
            return val
        except (ValueError, InvalidOperation) as ve:
            raise serializers.ValidationError(str(ve))

def validate_integer(val):
    if val:
        try:
            val = int(val)
            return val
        except ValueError as ve:
            raise serializers.ValidationError(str(ve))

def validate_gte(minimum):
    minimum = minimum
    def _validate_gte(val):
        if val and (val < minimum):
                raise serializers.ValidationError(
                    'Field must be > %s' % minimum)
        return val
    return _validate_gte

def validate_present(val, message=None):
    message = message or 'is required'
    if not val:
        raise serializers.ValidationError(message)
    return val

def validate_date(val):
    val = val.strip()
    if val:
        try:
            val = parse(val, dayfirst=True)
            return val
        except (ValueError, TypeError) as ve:
            raise serializers.ValidationError('%s is not a valid date' % val)

def validate_not_present(val, message=None):
    message = message or 'Field should not be present'
    if val.strip():
        raise serializers.ValidationError(message)
    return val

def validate_not_current_month(val):
    if val:
        now = datetime.datetime.now().replace(day=1).date()
        val_month = val.replace(day=1).date()
        if now == val_month:
            raise serializers.ValidationError('Date (%s) must not be from current month' % val.date())
    return val

def validate_regex(regex, flags=None):
    compiled_re = re.compile(regex, flags)

    def _validate_regex(val):
        if val and (not compiled_re.match(val)):
            raise serializers.ValidationError(
                'Field value (%s) doesn\'t match pattern: %s' % (val, regex))
        return val

    return _validate_regex

validate_postcode = validate_regex(POSTCODE_RE,
                                   flags=re.VERBOSE | re.I)

def validate_in(iterable):
    def _validate_in(val):
        if val and (not val in iterable):
            raise serializers.ValidationError(
                '%s must be one of %s' % (val, ', '.join(iterable)))
        return val

    return _validate_in

def inverted_reduce(x, f):
    return f(x)

TRUTHY = lambda x: bool(x)
FALSEY = lambda x: not bool(x)
NOT_EQUAL = lambda y: lambda x: x != y
AFTER_APR_2013 = lambda x: x and x > datetime.datetime(2013, 4, 1)

class depends_on(object):
    """
    A decorator to run a function if and only if the
    second arg passed to that function contains a key
    that passes a check.

    >>> d = {'a': True}
    >>> class A(object):
    >>>     @depends_on('a', check=TRUTHY)
    >>>     def do_something(self, d):
    >>>         return 1

    The above will only run if a in dict d is passes the check 'TRUTHY'

    This can be improved to be more generic by supporting plain functions and
    instance methods, also allowing user to specify the arg or kwarg to expect
    a dict instead of hard coding args[1]

    """

    def __init__(self, depends_field, check=None):
        self.depends_field = depends_field
        if not check:
            self.check = TRUTHY
        else:
            self.check = check

    def __call__(self, f):
        def wrapped_f(*args, **kwargs):
            assert isinstance(args[1], types.DictType)
            if self.check(args[1].get(self.depends_field)):
                return f(*args, **kwargs)
        return wrapped_f

def excel_col_name(col): # col is 1 based
    excel_col = ''
    div = col
    while div:
        (div, mod) = divmod(div-1, 26) # will return (x, 0 .. 25)
        excel_col = chr(mod + 65) + excel_col
    return excel_col

class ProviderCSVValidator(object):
    # the index is the offset in the csv
    fields = (
        # 'name', iterable of validators to apply
        ('CLA Reference Number', [validate_present, validate_integer]),  # 1
        ('Client Ref', [validate_present]),  # 2
        ('Account Number', [validate_present,
                            validate_regex(r'\d{1}[a-z]{1}\d{3}[a-z]{1}',
                                           flags=re.IGNORECASE)]),  #3
        ('First Name', [validate_present]),  #4
        ('Surname', [validate_present]),  #5
        ('DOB', [validate_date]),  #6
        ('Age Range', [validate_present, validate_in(AGE_RANGE)]),  #7
        ('Gender', [validate_present]),  #8
        ('Ethnicity', [validate_present]),  #9
        ('Unused1', [validate_not_present]),  #10
        ('Unused2', [validate_not_present]),  #11
        ('Postcode', [validate_present, validate_postcode]),
        #12
        ('Eligibility Code', [validate_in(ELIGIBILITY_CODES)]),  #13
        ('Matter Type 1', [validate_present, validate_in(VALID_MATTER_TYPE1)]),
        #14
        ('Matter Type 2', [validate_present, validate_in(VALID_MATTER_TYPE2)]),
        #15
        ('Stage Reached', [validate_in(VALID_STAGE_REACHED)]),  #16
        ('Outcome Code', [validate_in(VALID_OUTCOMES)]),  #17
        ('Unused3', [validate_not_present]),  #18
        ('Date Opened', [validate_date]),  #19
        ('Date Closed', [validate_date, validate_not_current_month]),  #20
        ('Time Spent', [validate_present, validate_integer, validate_gte(0)]),  #21
        ('Case Costs', [validate_present, validate_decimal]),  #22
        ('Unused4', [validate_not_present]),  #23
        ('Disability Code',
         [validate_present, validate_in(DISABILITY_INDICATOR)]),  #24
        ('Disbursements', [validate_decimal]),  #25
        ('Travel Costs', [validate_decimal]),  #26
        ('Determination', [validate_in(DETERMINATION_CODES)]),  #27
        ('Suitable for Telephone Advice', [validate_in({u'Y', u'N'})]),  #28
        ('Exceptional Cases (ref)', [validate_regex(r'\d{7}[a-z]{2}', re.I)]),
        #29
        ('Exempted Reason Code', [validate_in(EXEMPTION_CODES)]),  #30
        ('Adjustments / Adaptations', [validate_in(SERVICE_ADAPTATIONS)]),  #31
        ('Signposting / Referral', []),  #32
        ('Media Code', []),
        #33 TODO: Maybe put [validate_present]) back depending on reply from Alex A.
        ('Telephone / Online', [validate_present, validate_in(ADVICE_TYPES)]),
        # 34
    )

    def __init__(self, rows):
        self.rows = rows
        self.cleaned_data = []

    def _validate_field(self, field_name, field_value, idx,
                        row_num, validators):
        # Field Validation
        try:
            # reduce the validators over the original field value, save
            # the final value into self.cleaned_data[field_name]
            cleaned_value = reduce(inverted_reduce, validators,
                                   field_value)

            return cleaned_value

        except serializers.ValidationError as ve:
            ve.message = 'Row: %s Field (%s / %s): %s - %s' % (
                row_num + 1, idx + 1, excel_col_name(idx + 1), field_name, ve.message)
            raise ve

    def _validate_fields(self):
        """
        Validate individual field values, like django's clean_<fieldname> with
        less magic ( no setattr('clean_'+field_name) junk) just loop over
        the fields and apply the validators specified in the
        field spec (self.fields)
        """
        cleaned_data = {}
        errors = ErrorList()

        for row_num, row in enumerate(self.rows):
            if len(row) != len(self.fields):
                raise serializers.ValidationError(
                    'Row: %s - Incorrect number of columns should be %s '
                    'actually %s' % (row_num + 1, len(self.fields), len(row)))

            for idx, field_value in enumerate(row):
                field_name, validators = self.fields[idx]
                try:
                    cleaned_data[field_name] = self._validate_field(field_name,
                                                                field_value.strip(),
                                                                idx, row_num,
                                                                validators)
                except serializers.ValidationError as ve:
                    errors.append(ve)
            try:
                # Global Validation
                self.cleaned_data.append(
                    self._validate_data(cleaned_data, row_num)
                )
            except serializers.ValidationError as ve:
                errors.extend(ve.error_list)

        if len(errors):
            raise serializers.ValidationError(errors)

    def _validate_open_closed_date(self, cleaned_data):
        opened = cleaned_data.get('Date Opened')
        closed = cleaned_data.get('Date Closed')
        if (opened and closed) and (closed < opened):
            raise serializers.ValidationError(
                'Closed date (%s) must be after opened date (%s)' % (
                    closed.date().isoformat(), opened.date().isoformat()))

    @depends_on('Determination', check=FALSEY)
    @depends_on('Date Opened', check=AFTER_APR_2013)
    def _validate_service_adaptation(self, cleaned_data):
        validate_present(cleaned_data.get('Adjustments / Adaptations'),
                         message='Adjustments / Adaptations field is required')

    @depends_on('Determination', check=FALSEY)
    def _validate_media_code(self, cleaned_data):
        validate_present(cleaned_data.get('Media Code'),
                         message='Media Code is required because no determination was specified')

    @depends_on('Determination', check=FALSEY)
    def _validate_eligibility_code(self, cleaned_data):
        code = cleaned_data.get('Eligibility Code')
        time_spent = cleaned_data.get('Time Spent', 0)
        validate_present(code,
                         message='Eligibility Code field is required because no determination was specified')
        # check time spent
        if code in {u'S', u'W', u'X', u'Z'} and time_spent > 132:
            raise serializers.ValidationError(
                u'The eligibility code (%s) you have entered is not valid with '
                u'the time spent (%s) on this case, please review the '
                u'eligibility code.' % (code, time_spent))

    def _validate_category_consistency(self, cleaned_data):
        mt1, mt2, outcome, stage = cleaned_data.get('Matter Type 1'), \
                                   cleaned_data.get('Matter Type 2'), \
                                   cleaned_data.get('Outcome Code'), \
                                   cleaned_data.get('Stage Reached')

        category_dependent_fields = [mt1, mt2, outcome, stage]
        prefixes = {x[0] for x in category_dependent_fields if x}
        if not len(prefixes) == 1:
            raise serializers.ValidationError(
                'Matter Type 1 (%s), Matter Type 2 (%s), Outcome Code (%s) and Stage Reached (%s) fields must be of the same category.' % (
                    mt1, mt2, outcome, stage))

        return PREFIX_CATEGORY_LOOKUP[list(prefixes)[0]]

    @depends_on('Age Range', check=NOT_EQUAL(u'U'))
    def _validate_dob_present(self, cleaned_data):
        validate_present(cleaned_data.get('DOB'), "A date of birth is required unless"
                                                  " Age range is set to 'U'")

    @depends_on('Determination', check=TRUTHY)
    def _validate_time_spent(self, cleaned_data, category):
        MAX_TIME_ALLOWED = 18
        if category == u'discrimination':
            MAX_TIME_ALLOWED = 42

        time_spent_in_minutes = cleaned_data.get('Time Spent', 0)
        if time_spent_in_minutes > MAX_TIME_ALLOWED:
            raise serializers.ValidationError('Time spent (%s) must not be greater than %s minutes' % (time_spent_in_minutes, MAX_TIME_ALLOWED))

    @depends_on('Exempted Code Reason', check=TRUTHY)
    @depends_on('Determination', check=FALSEY)
    def _validate_exemption(self, cleaned_data, category):
        EXEMPT_CATEGORIES = {u'debt', u'discrimination', u'education'}
        if cleaned_data.get('Date Opened') > datetime.datetime(2013, 4, 1) and category in EXEMPT_CATEGORIES:
            validate_present(
                cleaned_data.get('Exempted Code Reason', cleaned_data.get('CLA Reference Number')),
                message='Exempt Code Reason or CLA Reference number required before case was opened after 1st Apr 2013'
            )

        if category not in EXEMPT_CATEGORIES:
            raise serializers.ValidationError(
                'An Exemption Reason can only be entered for Debt, '
                'Discrimination and Education matters')

    def _validate_telephone_or_online_advice(self, cleaned_data, category):
        ta = cleaned_data.get('Telephone / Online')
        if category not in {u'education', u'discrimination'} and ta == u'FF':
            raise serializers.ValidationError(
                'code FF only valid for Telephone Advice/Online Advice field if '
                'category is Education or Discrimination'
            )

    @depends_on('Determination', check=FALSEY)
    def _validate_stage_reached(self, cleaned_data):
        mt1 = cleaned_data.get('Matter Type 1')
        stage_reached_code = cleaned_data.get('Stage Reached')

        if mt1 in STAGE_REACHED_REQUIRED_MT1S:
            validate_present(stage_reached_code,
                             message='Field "Stage Reached" is required because Matter Type 1: %s was specified' % mt1)
        if mt1 in STAGE_REACHED_NOT_ALLOWED_MT1S:
            validate_not_present(stage_reached_code,
                                 message='Field "Stage Reached" is not allowed because Matter Type 1: %s was specified' % mt1)

    def _validate_data(self, cleaned_data, row_num):
        """
        Like django's clean method, use this to validate across fields
        """
        def _format_message(s):
            return 'Row: %s - %s' % (row_num + 1, s)

        errors = ErrorList()

        validation_methods = [
            self._validate_open_closed_date,
            self._validate_service_adaptation,
            self._validate_media_code,
            self._validate_eligibility_code,
            self._validate_stage_reached,
            self._validate_dob_present,
        ]
        validation_methods_depend_on_category = [
            self._validate_time_spent,
            self._validate_exemption,
            self._validate_telephone_or_online_advice
        ]

        for m in validation_methods:
            try:
                m(cleaned_data)
            except serializers.ValidationError as ve:
                errors.append(_format_message(ve.message))

        try:
            category = self._validate_category_consistency(cleaned_data)
        except serializers.ValidationError as ve:
            errors.append(_format_message(ve.message))
            raise serializers.ValidationError(errors)

        for m in validation_methods_depend_on_category:
            try:
                m(cleaned_data, category)
            except serializers.ValidationError as ve:
                errors.append(_format_message(ve.message))

        if len(errors):
            raise serializers.ValidationError(errors)

        return cleaned_data

    def validate(self):
        self._validate_fields()
        return self.cleaned_data
<commit_msg>add validation so year in csv upload must have 4 digits (not 2 as has been done)<commit_after>import datetime
from decimal import Decimal, InvalidOperation
from django.forms.util import ErrorList
import types
import re

from rest_framework import serializers
from dateutil.parser import parse

from legalaid.utils.csvupload.constants import AGE_RANGE, POSTCODE_RE, \
    ELIGIBILITY_CODES, DISABILITY_INDICATOR, EXEMPTION_CODES, \
    SERVICE_ADAPTATIONS, ADVICE_TYPES, VALID_OUTCOMES, VALID_STAGE_REACHED, \
    VALID_MATTER_TYPE1, VALID_MATTER_TYPE2, DETERMINATION_CODES, \
    PREFIX_CATEGORY_LOOKUP, STAGE_REACHED_NOT_ALLOWED_MT1S, STAGE_REACHED_REQUIRED_MT1S

date_pattern = re.compile("^[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}$")

def validate_decimal(val):
    if val:
        try:
            val = val.replace(u',', u'')
            val = Decimal(val)
            return val
        except (ValueError, InvalidOperation) as ve:
            raise serializers.ValidationError(str(ve))

def validate_integer(val):
    if val:
        try:
            val = int(val)
            return val
        except ValueError as ve:
            raise serializers.ValidationError(str(ve))

def validate_gte(minimum):
    minimum = minimum
    def _validate_gte(val):
        if val and (val < minimum):
                raise serializers.ValidationError(
                    'Field must be > %s' % minimum)
        return val
    return _validate_gte

def validate_present(val, message=None):
    message = message or 'is required'
    if not val:
        raise serializers.ValidationError(message)
    return val

def validate_date(val):
    val = val.strip()
    if val:
        try:
            assert date_pattern.match(val)
            # day, month, year = val.split('/')
            val = parse(val, dayfirst=True)
            return val
        except (ValueError, TypeError, AssertionError) as ve:
            raise serializers.ValidationError('%s is not a valid date (DD/MM/YYYY)' % val)

def validate_not_present(val, message=None):
    message = message or 'Field should not be present'
    if val.strip():
        raise serializers.ValidationError(message)
    return val

def validate_not_current_month(val):
    if val:
        now = datetime.datetime.now().replace(day=1).date()
        val_month = val.replace(day=1).date()
        if now == val_month:
            raise serializers.ValidationError('Date (%s) must not be from current month' % val.date())
    return val

def validate_regex(regex, flags=None):
    compiled_re = re.compile(regex, flags)

    def _validate_regex(val):
        if val and (not compiled_re.match(val)):
            raise serializers.ValidationError(
                'Field value (%s) doesn\'t match pattern: %s' % (val, regex))
        return val

    return _validate_regex

validate_postcode = validate_regex(POSTCODE_RE,
                                   flags=re.VERBOSE | re.I)

def validate_in(iterable):
    def _validate_in(val):
        if val and (not val in iterable):
            raise serializers.ValidationError(
                '%s must be one of %s' % (val, ', '.join(iterable)))
        return val

    return _validate_in

def inverted_reduce(x, f):
    return f(x)

TRUTHY = lambda x: bool(x)
FALSEY = lambda x: not bool(x)
NOT_EQUAL = lambda y: lambda x: x != y
AFTER_APR_2013 = lambda x: x and x > datetime.datetime(2013, 4, 1)

class depends_on(object):
    """
    A decorator to run a function if and only if the
    second arg passed to that function contains a key
    that passes a check.

    >>> d = {'a': True}
    >>> class A(object):
    >>>     @depends_on('a', check=TRUTHY)
    >>>     def do_something(self, d):
    >>>         return 1

    The above will only run if a in dict d is passes the check 'TRUTHY'

    This can be improved to be more generic by supporting plain functions and
    instance methods, also allowing user to specify the arg or kwarg to expect
    a dict instead of hard coding args[1]

    """

    def __init__(self, depends_field, check=None):
        self.depends_field = depends_field
        if not check:
            self.check = TRUTHY
        else:
            self.check = check

    def __call__(self, f):
        def wrapped_f(*args, **kwargs):
            assert isinstance(args[1], types.DictType)
            if self.check(args[1].get(self.depends_field)):
                return f(*args, **kwargs)
        return wrapped_f

def excel_col_name(col): # col is 1 based
    excel_col = ''
    div = col
    while div:
        (div, mod) = divmod(div-1, 26) # will return (x, 0 .. 25)
        excel_col = chr(mod + 65) + excel_col
    return excel_col

class ProviderCSVValidator(object):
    # the index is the offset in the csv
    fields = (
        # 'name', iterable of validators to apply
        ('CLA Reference Number', [validate_present, validate_integer]),  # 1
        ('Client Ref', [validate_present]),  # 2
        ('Account Number', [validate_present,
                            validate_regex(r'\d{1}[a-z]{1}\d{3}[a-z]{1}',
                                           flags=re.IGNORECASE)]),  #3
        ('First Name', [validate_present]),  #4
        ('Surname', [validate_present]),  #5
        ('DOB', [validate_date]),  #6
        ('Age Range', [validate_present, validate_in(AGE_RANGE)]),  #7
        ('Gender', [validate_present]),  #8
        ('Ethnicity', [validate_present]),  #9
        ('Unused1', [validate_not_present]),  #10
        ('Unused2', [validate_not_present]),  #11
        ('Postcode', [validate_present, validate_postcode]),
        #12
        ('Eligibility Code', [validate_in(ELIGIBILITY_CODES)]),  #13
        ('Matter Type 1', [validate_present, validate_in(VALID_MATTER_TYPE1)]),
        #14
        ('Matter Type 2', [validate_present, validate_in(VALID_MATTER_TYPE2)]),
        #15
        ('Stage Reached', [validate_in(VALID_STAGE_REACHED)]),  #16
        ('Outcome Code', [validate_in(VALID_OUTCOMES)]),  #17
        ('Unused3', [validate_not_present]),  #18
        ('Date Opened', [validate_date]),  #19
        ('Date Closed', [validate_date, validate_not_current_month]),  #20
        ('Time Spent', [validate_present, validate_integer, validate_gte(0)]),  #21
        ('Case Costs', [validate_present, validate_decimal]),  #22
        ('Unused4', [validate_not_present]),  #23
        ('Disability Code',
         [validate_present, validate_in(DISABILITY_INDICATOR)]),  #24
        ('Disbursements', [validate_decimal]),  #25
        ('Travel Costs', [validate_decimal]),  #26
        ('Determination', [validate_in(DETERMINATION_CODES)]),  #27
        ('Suitable for Telephone Advice', [validate_in({u'Y', u'N'})]),  #28
        ('Exceptional Cases (ref)', [validate_regex(r'\d{7}[a-z]{2}', re.I)]),
        #29
        ('Exempted Reason Code', [validate_in(EXEMPTION_CODES)]),  #30
        ('Adjustments / Adaptations', [validate_in(SERVICE_ADAPTATIONS)]),  #31
        ('Signposting / Referral', []),  #32
        ('Media Code', []),
        #33 TODO: Maybe put [validate_present]) back depending on reply from Alex A.
        ('Telephone / Online', [validate_present, validate_in(ADVICE_TYPES)]),
        # 34
    )

    def __init__(self, rows):
        self.rows = rows
        self.cleaned_data = []

    def _validate_field(self, field_name, field_value, idx,
                        row_num, validators):
        # Field Validation
        try:
            # reduce the validators over the original field value, save
            # the final value into self.cleaned_data[field_name]
            cleaned_value = reduce(inverted_reduce, validators,
                                   field_value)

            return cleaned_value

        except serializers.ValidationError as ve:
            ve.message = 'Row: %s Field (%s / %s): %s - %s' % (
                row_num + 1, idx + 1, excel_col_name(idx + 1), field_name, ve.message)
            raise ve

    def _validate_fields(self):
        """
        Validate individual field values, like django's clean_<fieldname> with
        less magic ( no setattr('clean_'+field_name) junk) just loop over
        the fields and apply the validators specified in the
        field spec (self.fields)
        """
        cleaned_data = {}
        errors = ErrorList()

        for row_num, row in enumerate(self.rows):
            if len(row) != len(self.fields):
                raise serializers.ValidationError(
                    'Row: %s - Incorrect number of columns should be %s '
                    'actually %s' % (row_num + 1, len(self.fields), len(row)))

            for idx, field_value in enumerate(row):
                field_name, validators = self.fields[idx]
                try:
                    cleaned_data[field_name] = self._validate_field(field_name,
                                                                field_value.strip(),
                                                                idx, row_num,
                                                                validators)
                except serializers.ValidationError as ve:
                    errors.append(ve)
            try:
                # Global Validation
                self.cleaned_data.append(
                    self._validate_data(cleaned_data, row_num)
                )
            except serializers.ValidationError as ve:
                errors.extend(ve.error_list)

        if len(errors):
            raise serializers.ValidationError(errors)

    def _validate_open_closed_date(self, cleaned_data):
        opened = cleaned_data.get('Date Opened')
        closed = cleaned_data.get('Date Closed')
        if (opened and closed) and (closed < opened):
            raise serializers.ValidationError(
                'Closed date (%s) must be after opened date (%s)' % (
                    closed.date().isoformat(), opened.date().isoformat()))

    @depends_on('Determination', check=FALSEY)
    @depends_on('Date Opened', check=AFTER_APR_2013)
    def _validate_service_adaptation(self, cleaned_data):
        validate_present(cleaned_data.get('Adjustments / Adaptations'),
                         message='Adjustments / Adaptations field is required')

    @depends_on('Determination', check=FALSEY)
    def _validate_media_code(self, cleaned_data):
        validate_present(cleaned_data.get('Media Code'),
                         message='Media Code is required because no determination was specified')

    @depends_on('Determination', check=FALSEY)
    def _validate_eligibility_code(self, cleaned_data):
        code = cleaned_data.get('Eligibility Code')
        time_spent = cleaned_data.get('Time Spent', 0)
        validate_present(code,
                         message='Eligibility Code field is required because no determination was specified')
        # check time spent
        if code in {u'S', u'W', u'X', u'Z'} and time_spent > 132:
            raise serializers.ValidationError(
                u'The eligibility code (%s) you have entered is not valid with '
                u'the time spent (%s) on this case, please review the '
                u'eligibility code.' % (code, time_spent))

    def _validate_category_consistency(self, cleaned_data):
        mt1, mt2, outcome, stage = cleaned_data.get('Matter Type 1'), \
                                   cleaned_data.get('Matter Type 2'), \
                                   cleaned_data.get('Outcome Code'), \
                                   cleaned_data.get('Stage Reached')

        category_dependent_fields = [mt1, mt2, outcome, stage]
        prefixes = {x[0] for x in category_dependent_fields if x}
        if not len(prefixes) == 1:
            raise serializers.ValidationError(
                'Matter Type 1 (%s), Matter Type 2 (%s), Outcome Code (%s) and Stage Reached (%s) fields must be of the same category.' % (
                    mt1, mt2, outcome, stage))

        return PREFIX_CATEGORY_LOOKUP[list(prefixes)[0]]

    @depends_on('Age Range', check=NOT_EQUAL(u'U'))
    def _validate_dob_present(self, cleaned_data):
        validate_present(cleaned_data.get('DOB'), "A date of birth is required unless"
                                                  " Age range is set to 'U'")

    @depends_on('Determination', check=TRUTHY)
    def _validate_time_spent(self, cleaned_data, category):
        MAX_TIME_ALLOWED = 18
        if category == u'discrimination':
            MAX_TIME_ALLOWED = 42

        time_spent_in_minutes = cleaned_data.get('Time Spent', 0)
        if time_spent_in_minutes > MAX_TIME_ALLOWED:
            raise serializers.ValidationError('Time spent (%s) must not be greater than %s minutes' % (time_spent_in_minutes, MAX_TIME_ALLOWED))

    @depends_on('Exempted Code Reason', check=TRUTHY)
    @depends_on('Determination', check=FALSEY)
    def _validate_exemption(self, cleaned_data, category):
        EXEMPT_CATEGORIES = {u'debt', u'discrimination', u'education'}
        if cleaned_data.get('Date Opened') > datetime.datetime(2013, 4, 1) and category in EXEMPT_CATEGORIES:
            validate_present(
                cleaned_data.get('Exempted Code Reason', cleaned_data.get('CLA Reference Number')),
                message='Exempt Code Reason or CLA Reference number required before case was opened after 1st Apr 2013'
            )

        if category not in EXEMPT_CATEGORIES:
            raise serializers.ValidationError(
                'An Exemption Reason can only be entered for Debt, '
                'Discrimination and Education matters')

    def _validate_telephone_or_online_advice(self, cleaned_data, category):
        ta = cleaned_data.get('Telephone / Online')
        if category not in {u'education', u'discrimination'} and ta == u'FF':
            raise serializers.ValidationError(
                'code FF only valid for Telephone Advice/Online Advice field if '
                'category is Education or Discrimination'
            )

    @depends_on('Determination', check=FALSEY)
    def _validate_stage_reached(self, cleaned_data):
        mt1 = cleaned_data.get('Matter Type 1')
        stage_reached_code = cleaned_data.get('Stage Reached')

        if mt1 in STAGE_REACHED_REQUIRED_MT1S:
            validate_present(stage_reached_code,
                             message='Field "Stage Reached" is required because Matter Type 1: %s was specified' % mt1)
        if mt1 in STAGE_REACHED_NOT_ALLOWED_MT1S:
            validate_not_present(stage_reached_code,
                                 message='Field "Stage Reached" is not allowed because Matter Type 1: %s was specified' % mt1)

    def _validate_data(self, cleaned_data, row_num):
        """
        Like django's clean method, use this to validate across fields
        """
        def _format_message(s):
            return 'Row: %s - %s' % (row_num + 1, s)

        errors = ErrorList()

        validation_methods = [
            self._validate_open_closed_date,
            self._validate_service_adaptation,
            self._validate_media_code,
            self._validate_eligibility_code,
            self._validate_stage_reached,
            self._validate_dob_present,
        ]
        validation_methods_depend_on_category = [
            self._validate_time_spent,
            self._validate_exemption,
            self._validate_telephone_or_online_advice
        ]

        for m in validation_methods:
            try:
                m(cleaned_data)
            except serializers.ValidationError as ve:
                errors.append(_format_message(ve.message))

        try:
            category = self._validate_category_consistency(cleaned_data)
        except serializers.ValidationError as ve:
            errors.append(_format_message(ve.message))
            raise serializers.ValidationError(errors)

        for m in validation_methods_depend_on_category:
            try:
                m(cleaned_data, category)
            except serializers.ValidationError as ve:
                errors.append(_format_message(ve.message))

        if len(errors):
            raise serializers.ValidationError(errors)

        return cleaned_data

    def validate(self):
        self._validate_fields()
        return self.cleaned_data
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from dateutil.relativedelta import relativedelta
from django.db import transaction
from django.core.mail import send_mail
from django.conf import settings
from django.template import loader
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone

from account.models import User
from common.consts import COMPLETED_REASON
from notification.models import Notification, NotifiedUser
from notification.consts import NOTIFICATION_DATA, NotificationType

@transaction.atomic
def feed_alert(notification_type, subject, body, users, obj):
    notification = Notification.objects.create(
        name=subject,
        description=body,
        source=notification_type,
        content_object=obj,
    )
    notified_users = []
    for user in users:
        notified_users.append(NotifiedUser(notification=notification, did_read=False, recipient_id=user.id))
    NotifiedUser.objects.bulk_create(notified_users)

def send_notification(
        notification_type, obj, users, context=None, send_in_feed=True, check_sent_for_source=True
):
    """
    notification_type - check NotificationType class in const.py
    obj - object directly associated w/ notification. generic fk to it
    users - users who are receiving notif
    context - context to provide to template for email or body of notif
    send_in_feed - create notification feed element
    check_sent_for_source - checks to confirm no duplicates are sent for source + object. false bypasses

    """

    if check_sent_for_source and notification_already_sent(obj, notification_type):
        return

    notification_info = NOTIFICATION_DATA.get(notification_type)

    targets = [u.email for u in users]
    body = render_notification_template_to_str(notification_info.get('template_name'), context)
    send_mail(notification_info.get('subject'), body, settings.DEFAULT_FROM_EMAIL, targets)

    if send_in_feed:
        feed_alert(notification_type, notification_info.get('subject'), body, users, obj)

def render_notification_template_to_str(template_name, context):
    return loader.get_template('notifications/{}'.format(template_name)).render(context)

# Return all partner members. Potential limit in future?
def get_notify_partner_users_for_application(application):
    users = User.objects.filter(partner_members__partner=application.partner)
    return users.distinct()

def get_partner_users_for_app_qs(application_qs):
    notify_user_ids = []
    for application in application_qs:
        users_qs = get_notify_partner_users_for_application(application)
        notify_user_ids.extend(list(users_qs.values_list('id', flat=True)))

    return User.objects.filter(id__in=notify_user_ids)

# We don't want to send 2x of the same notification
def notification_already_sent(obj, notification_type):
    content_type = ContentType.objects.get_for_model(obj)
    return Notification.objects.filter(object_id=obj.id,
                                       source=notification_type,
                                       content_type=content_type).exists()

def user_received_notification_recently(user, obj, notification_type, time_ago=relativedelta(days=1)):
    content_type = ContentType.objects.get_for_model(obj)
    return NotifiedUser.objects.filter(
        notification__source=notification_type,
        notification__object_id=obj.id,
        notification__content_type=content_type,
        recipient=user,
        created__gte=timezone.now() - time_ago
    ).exists()

def send_notification_cfei_completed(eoi):
    if eoi.completed_reason == COMPLETED_REASON.canceled:
        users = get_partner_users_for_app_qs(eoi.applications.all())
        send_notification('cfei_cancel', eoi, users)

    if eoi.completed_reason == COMPLETED_REASON.partners:
        users = get_partner_users_for_app_qs(eoi.applications.losers())
        send_notification('cfei_application_lost', eoi, users)

    if eoi.completed_reason == COMPLETED_REASON.no_candidate:
        # TODO - perhaps a different message?
        users = get_partner_users_for_app_qs(eoi.applications.all())
        send_notification('cfei_application_lost', eoi, users)

def send_notification_application_updated(application):

    if application.eoi.is_open:
        # if did win
        if application.did_win:
            users = get_notify_partner_users_for_application(application)
            send_notification('cfei_application_selected', application, users)

        # if did withdraw
        if application.did_withdraw:
            users = get_notify_partner_users_for_application(application)
            send_notification('cfei_application_withdraw', application, users)

def send_notificiation_application_created(application):

    if application.eoi:
        if application.eoi.is_open:
            users = get_notify_partner_users_for_application(application)
            send_notification('cfei_application_submitted', application, users)

        if application.eoi.is_direct:
            users = get_notify_partner_users_for_application(application)
            send_notification('unsol_application_submitted', application, users)

        if application.eoi.is_direct and application.eoi.unsolicited_conversion:
            users = get_notify_partner_users_for_application(application)
            send_notification('direct_select_un_int', application, users,
                              context={'eoi_title': application.eoi.title})

        # Alert Agency Users if accept / decline
        if application.did_win:
            if application.did_accept or application.did_decline:
                notify_ids = list(application.eoi.focal_points.all().values_list('id', flat=True))
                notify_ids.append(application.eoi.created_by.id)
                notify_users = User.objects.filter(id__in=notify_ids).distinct()
                send_notification('agency_application_decision_made', application, notify_users)

    else:
        if application.is_unsolicited:
            users = get_notify_partner_users_for_application(application)
            send_notification('direct_select_ucn', application, users)

def send_cfei_review_required_notification(eoi, users):
    send_notification(
        NotificationType.CFEI_REVIEW_REQUIRED, eoi, users, send_in_feed=True, context={
            'eoi_name': eoi.title,
            'eoi_url': eoi.get_absolute_url()
        }
    )
<commit_msg>remove check for cfei review reuqired notification<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from dateutil.relativedelta import relativedelta
from django.db import transaction
from django.core.mail import send_mail
from django.conf import settings
from django.template import loader
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone

from account.models import User
from common.consts import COMPLETED_REASON
from notification.models import Notification, NotifiedUser
from notification.consts import NOTIFICATION_DATA, NotificationType

@transaction.atomic
def feed_alert(notification_type, subject, body, users, obj):
    notification = Notification.objects.create(
        name=subject,
        description=body,
        source=notification_type,
        content_object=obj,
    )
    notified_users = []
    for user in users:
        notified_users.append(NotifiedUser(notification=notification, did_read=False, recipient_id=user.id))
    NotifiedUser.objects.bulk_create(notified_users)

def send_notification(
        notification_type, obj, users, context=None, send_in_feed=True, check_sent_for_source=True
):
    """
    notification_type - check NotificationType class in const.py
    obj - object directly associated w/ notification. generic fk to it
    users - users who are receiving notif
    context - context to provide to template for email or body of notif
    send_in_feed - create notification feed element
    check_sent_for_source - checks to confirm no duplicates are sent for source + object. false bypasses

    """

    if check_sent_for_source and notification_already_sent(obj, notification_type):
        return

    notification_info = NOTIFICATION_DATA.get(notification_type)

    targets = [u.email for u in users]
    body = render_notification_template_to_str(notification_info.get('template_name'), context)
    send_mail(notification_info.get('subject'), body, settings.DEFAULT_FROM_EMAIL, targets)

    if send_in_feed:
        feed_alert(notification_type, notification_info.get('subject'), body, users, obj)

def render_notification_template_to_str(template_name, context):
    return loader.get_template('notifications/{}'.format(template_name)).render(context)

# Return all partner members. Potential limit in future?
def get_notify_partner_users_for_application(application):
    users = User.objects.filter(partner_members__partner=application.partner)
    return users.distinct()

def get_partner_users_for_app_qs(application_qs):
    notify_user_ids = []
    for application in application_qs:
        users_qs = get_notify_partner_users_for_application(application)
        notify_user_ids.extend(list(users_qs.values_list('id', flat=True)))

    return User.objects.filter(id__in=notify_user_ids)

# We don't want to send 2x of the same notification
def notification_already_sent(obj, notification_type):
    content_type = ContentType.objects.get_for_model(obj)
    return Notification.objects.filter(object_id=obj.id,
                                       source=notification_type,
                                       content_type=content_type).exists()

def user_received_notification_recently(user, obj, notification_type, time_ago=relativedelta(days=1)):
    content_type = ContentType.objects.get_for_model(obj)
    return NotifiedUser.objects.filter(
        notification__source=notification_type,
        notification__object_id=obj.id,
        notification__content_type=content_type,
        recipient=user,
        created__gte=timezone.now() - time_ago
    ).exists()

def send_notification_cfei_completed(eoi):
    if eoi.completed_reason == COMPLETED_REASON.canceled:
        users = get_partner_users_for_app_qs(eoi.applications.all())
        send_notification('cfei_cancel', eoi, users)

    if eoi.completed_reason == COMPLETED_REASON.partners:
        users = get_partner_users_for_app_qs(eoi.applications.losers())
        send_notification('cfei_application_lost', eoi, users)

    if eoi.completed_reason == COMPLETED_REASON.no_candidate:
        # TODO - perhaps a different message?
        users = get_partner_users_for_app_qs(eoi.applications.all())
        send_notification('cfei_application_lost', eoi, users)

def send_notification_application_updated(application):

    if application.eoi.is_open:
        # if did win
        if application.did_win:
            users = get_notify_partner_users_for_application(application)
            send_notification('cfei_application_selected', application, users)

        # if did withdraw
        if application.did_withdraw:
            users = get_notify_partner_users_for_application(application)
            send_notification('cfei_application_withdraw', application, users)

def send_notificiation_application_created(application):

    if application.eoi:
        if application.eoi.is_open:
            users = get_notify_partner_users_for_application(application)
            send_notification('cfei_application_submitted', application, users)

        if application.eoi.is_direct:
            users = get_notify_partner_users_for_application(application)
            send_notification('unsol_application_submitted', application, users)

        if application.eoi.is_direct and application.eoi.unsolicited_conversion:
            users = get_notify_partner_users_for_application(application)
            send_notification('direct_select_un_int', application, users,
                              context={'eoi_title': application.eoi.title})

        # Alert Agency Users if accept / decline
        if application.did_win:
            if application.did_accept or application.did_decline:
                notify_ids = list(application.eoi.focal_points.all().values_list('id', flat=True))
                notify_ids.append(application.eoi.created_by.id)
                notify_users = User.objects.filter(id__in=notify_ids).distinct()
                send_notification('agency_application_decision_made', application, notify_users)

    else:
        if application.is_unsolicited:
            users = get_notify_partner_users_for_application(application)
            send_notification('direct_select_ucn', application, users)

def send_cfei_review_required_notification(eoi, users):
    send_notification(
        NotificationType.CFEI_REVIEW_REQUIRED, eoi, users, send_in_feed=True, check_sent_for_source=False, context={
            'eoi_name': eoi.title,
            'eoi_url': eoi.get_absolute_url()
        }
    )
<|endoftext|>


<commit_before>#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# Author: R.F. Smith <rsmith@xs4all.nl>
# $Date$
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to tiff2pdf.py. This work is published from
# the Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/

"""Convert TIFF files to PDF format using the libtiff package."""

from __future__ import division, print_function  # Python 2 compatibility

__version__ = '$Revision$'[11:-2]

import os
import sys
import subprocess
from multiprocessing import cpu_count
from time import sleep

def checkfor(args, rv=0):
    """Make sure that a program necessary for using this script is
    available.

    :param args: String or list of strings of commands. A single string may
    not contain spaces.
    :param rv: Expected return value from evoking the command.
    """
    if isinstance(args, str):
        if ' ' in args:
            raise ValueError('no spaces in single command allowed')
        args = [args]
    try:
        with open(os.devnull, 'w') as bb:
            rc = subprocess.call(args, stdout=bb, stderr=bb)
        if rc != rv:
            raise OSError
    except OSError as oops:
        outs = "Required program '{}' not found: {}."
        print(outs.format(args[0], oops.strerror))
        sys.exit(1)

def convert(fname):
    """Convert the file named fname."""
    try:
        args = ['tiffinfo', fname]
        # Gather information about the TIFF file. pylint: disable=E1103
        txt = subprocess.check_output(args).decode().split()
        if 'Width:' not in txt:
            raise ValueError
        index = txt.index('Width:')
        width = float(txt[index+1])
        length = float(txt[index+4])
        xres = float(txt[index+6][:-1])
        yres = float(txt[index+7])
        # Create the output file name.
        if fname.endswith(('.tif', '.TIF')):
            outname = fname[:-4]
        elif fname.endswith(('.tiff', '.TIFF')):
            outname = fname[:-5]
        outname = outname.replace(' ', '_') + '.pdf'
        args = ['tiff2pdf', '-w', str(width/xres), '-l', str(length/xres),
                '-x', str(xres), '-y', str(yres), '-o', outname, fname]
        with open(os.devnull, 'w') as bitbucket:
            p = subprocess.Popen(args, stdout=bitbucket, stderr=bitbucket)
            print("Conversion of {} to {} started.".format(fname, outname))
        return (p, fname, outname)
    except:
        print("Starting conversion of {} failed.".format(fname))
        return (None, fname, None)

def manageprocs(proclist):
    """Check a list of subprocesses for processes that have ended and
    remove them from the list.
    """
    print('# of conversions running: {}\r'.format(len(proclist)), end='')
    sys.stdout.flush()
    for p in proclist:
        pr, ifn, ofn = p
        if pr is None:
            proclist.remove(p)
        if pr.poll() is not None:
            print('Conversion of {} to {} finished.'.format(ifn, ofn))
            proclist.remove(p)
    sleep(0.5)

def main(argv):
    """Main program.

    :param argv: command line arguments
    """
    if len(argv) == 1:
        binary = os.path.basename(argv[0])
        print("{} version {}".format(binary, __version__), file=sys.stderr)
        print("Usage: {} [file ...]".format(binary), file=sys.stderr)
        sys.exit(0)
    checkfor('tiffinfo', 255)
    checkfor(['tiff2pdf', '-v'])
    procs = []
    maxprocs = cpu_count()
    for fname in argv[1:]:
        while len(procs) == maxprocs:
            manageprocs(procs)
        procs.append(convert(fname))
    while len(procs) > 0:
        manageprocs(procs)

if __name__ == '__main__':
    main(sys.argv)
<commit_msg>Fixed tifftopdf if resolution is missing from TIFF file.<commit_after>#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# Author: R.F. Smith <rsmith@xs4all.nl>
# $Date$
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to tiff2pdf.py. This work is published from
# the Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/

"""Convert TIFF files to PDF format using the libtiff package."""

from __future__ import division, print_function  # Python 2 compatibility

__version__ = '$Revision$'[11:-2]

import os
import sys
import subprocess
from multiprocessing import cpu_count
from time import sleep

def checkfor(args, rv=0):
    """Make sure that a program necessary for using this script is
    available.

    :param args: String or list of strings of commands. A single string may
    not contain spaces.
    :param rv: Expected return value from evoking the command.
    """
    if isinstance(args, str):
        if ' ' in args:
            raise ValueError('no spaces in single command allowed')
        args = [args]
    try:
        with open(os.devnull, 'w') as bb:
            rc = subprocess.call(args, stdout=bb, stderr=bb)
        if rc != rv:
            raise OSError
    except OSError as oops:
        outs = "Required program '{}' not found: {}."
        print(outs.format(args[0], oops.strerror))
        sys.exit(1)

def convert(fname):
    """Convert the file named fname."""
    try:
        args = ['tiffinfo', fname]
        # Gather information about the TIFF file. pylint: disable=E1103
        txt = subprocess.check_output(args).decode().split()
        if 'Width:' not in txt:
            raise ValueError
        index = txt.index('Width:')
        width = float(txt[index+1])
        length = float(txt[index+4])
        try:
            index = txt.index('Resolution:')
            xres = float(txt[index+1][:-1])
            yres = float(txt[index+2])
        except ValueError:
            xres, yres = None, None
        # Create the output file name.
        if fname.endswith(('.tif', '.TIF')):
            outname = fname[:-4]
        elif fname.endswith(('.tiff', '.TIFF')):
            outname = fname[:-5]
        outname = outname.replace(' ', '_') + '.pdf'
        if xres:
            args = ['tiff2pdf', '-w', str(width/xres), '-l', str(length/xres),
                    '-x', str(xres), '-y', str(yres), '-o', outname, fname]
        else:
            args = ['tiff2pdf', '-o', outname, '-z', '-p', 'A4', '-F', fname]
            print("No resolution in {}. Fitting to A4.".format(fname))
        with open(os.devnull, 'w') as bitbucket:
            p = subprocess.Popen(args, stdout=bitbucket, stderr=bitbucket)
            print("Conversion of {} to {} started.".format(fname, outname))
        return (p, fname, outname)
    except Exception as e:
        print("Starting conversion of {} failed: {}".format(fname, str(e)))
        return (None, fname, None)

def manageprocs(proclist):
    """Check a list of subprocesses for processes that have ended and
    remove them from the list.
    """
    print('# of conversions running: {}\r'.format(len(proclist)), end='')
    sys.stdout.flush()
    for p in proclist:
        pr, ifn, ofn = p
        if pr is None:
            proclist.remove(p)
            break
        if pr.poll() is not None:
            print('Conversion of {} to {} finished.'.format(ifn, ofn))
            proclist.remove(p)
    sleep(0.5)

def main(argv):
    """Main program.

    :param argv: command line arguments
    """
    if len(argv) == 1:
        binary = os.path.basename(argv[0])
        print("{} version {}".format(binary, __version__), file=sys.stderr)
        print("Usage: {} [file ...]".format(binary), file=sys.stderr)
        sys.exit(0)
    checkfor('tiffinfo', 255)
    checkfor(['tiff2pdf', '-v'])
    procs = []
    maxprocs = cpu_count()
    for fname in argv[1:]:
        while len(procs) == maxprocs:
            manageprocs(procs)
        procs.append(convert(fname))
    while len(procs) > 0:
        manageprocs(procs)

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