<commit_before>'''
Views for the reporting functions. These will be used as endpoints when
creating the CSV reports.

Refactor this module to use a base view for CSV endpoints, which will
do the basic set up of creating a buffer object, inserting the BOM and
pushing it back to the user.
'''

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

import datetime
import csv

from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponse, Http404

from timetracker.utils.decorators import admin_check, loggedin, permissions
from timetracker.tracker.models import Tbluser, TrackingEntry
from timetracker.tracker.models import Tblauthorization as tblauth
from timetracker.utils.datemaps import (generate_select,
                                        generate_employee_box,
                                        generate_month_box,
                                        MONTH_MAP)
from timetracker.utils.writers import UnicodeWriter
from timetracker.tracker.management.commands import mec_ot_report

@admin_check
def reporting(request):
    '''Base reporting hub
    Generates all the select boxes and pre-filled text fields.
    '''
    user = Tbluser.objects.get(id=request.session.get("user_id"))
    return render_to_response(
        "reporting.html",
        {
            "employee_box": generate_employee_box(user),
            "yearbox_hol": datetime.datetime.now().year,
            "monthbox_hol": generate_month_box("monthbox_hol"),
            "monthbox_ot": generate_month_box("monthbox_ot"),
            "monthbox_hr": generate_month_box("monthbox_hr"),
            "monthbox_all": generate_month_box("monthbox_all"),
            "teambox_all": generate_select(Tbluser.MARKET_CHOICES, id="teambox_all"),
        },
        RequestContext(request))

@admin_check
def download_all_holiday_data(request, who=None):
    '''Endpoint which creates a CSV file for all holiday data for a
    single employee.

    :param who: The ID of the person the report should be generated for
    if the user is not in the span of control for the administrator then
    no report will be generated.'''
    if not who:
        raise Http404

    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    try:
        target_user = auth_user.get_subordinates().get(id=who)
    except Tbluser.DoesNotExist:
        raise Http404

    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(TrackingEntry.headings())

    for entry in TrackingEntry.objects.filter(user_id=who):
        csvfile.writerow(entry.display_as_csv())

    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=AllHolidayData_%s.csv' % target_user.id
    return response

@admin_check
def yearmonthhol(request, year=None, month=None):
    '''Endpoint which creates a CSV file for all holiday data within
    a specific month.

    :param year: The year for the report.
    :param month: The month for the report.

    :note: Both year and mont are required.'''
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(TrackingEntry.headings())
    for user in auth_user.get_subordinates():
        for entry in TrackingEntry.objects.filter(
            entry_date__year=year,
            entry_date__month=month,
            user_id=user.id):
            csvfile.writerow(entry.display_as_csv())
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=HolidayData_%s_%s.csv' % (year, month)
    return response

@admin_check
def ot_by_month(request, year=None, month=None):
    '''Endpoint which creates a CSV file for all OT in a given month

    :param year: The year for the report.
    :param month: The month for the report.

    :note: Both year and mont are required.'''
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(
        ["Name", "Team", MONTH_MAP[int(month)-1][1]]
        )
    total_balance = 0
    for user in auth_user.get_subordinates():
        balance = user.get_total_balance(ret='flo', year=year, month=month)
        total_balance += balance
        csvfile.writerow([user.name(), user.process, "%.2f" % balance])
    csvfile.writerow(["Total", "Total", "%.2f" % total_balance])
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=OT_By_Month_%s_%s.csv' % (year, month)
    return response

@admin_check
def ot_by_year(request, year=None):
    '''Endpoint which creates a CSV file for all OT in a year.
    :param year: The year for the report.'''
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(
        ["Name", "Team"] + [MONTH_MAP[n][1] for n in range(0,12)]
        )
    total_balance = 0
    balances = {
        n: 0 for n in range(1, 13)
        }
    for user in auth_user.get_subordinates():
        row = [user.name(), user.process]
        for month in range(1, 13):
            balance = user.get_total_balance(ret='flo', year=year, month=month)
            balances[month] += balance
            row.append("%.2f" % balance if balance != 0.0 else "-")
        csvfile.writerow(row)
    totalrow = ["Total", "Total"]
    [totalrow.append("%.2f" % balances[n]) for n in range(1,13)]
    csvfile.writerow(totalrow)
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=OT_By_Year_%s_%s.csv' % (year, month)
    return response

@admin_check
def holidays_for_yearmonth(request, year=None):
    '''Endpoint which creates a CSV file for all holidays per month
    in a year

    :param year: Year for the report.'''
    if not year:
        raise Http404
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(
        ["Name"] + [MONTH_MAP[n][1] for n in range(0,12)] + ["Used", "Remaining"]
        )
    for user in auth_user.get_subordinates():
        row = [user.name()]
        total = 0
        for month in range(1,13):
            e = TrackingEntry.objects.filter(user_id=user.id,
                                             entry_date__year=year,
                                             entry_date__month=month,
                                             daytype="HOLIS").count()
            total += e
            row.append(e)
        row.append(["%d" % total, "%d" % user.holiday_balance])
        csvfile.writerow(row)
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=Holidays_for_year%s.csv' % year
    return response

@admin_check
def ot_for_hr(request, year=None, month=None):
    if not year or not month:
        raise Http404
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    dt = datetime.datetime(year=int(year), month=int(month), day=1)
    return mec_ot_report.report_for_account(auth_user.market, dt, send=False)

@permissions(["SUPER"])
def all_team(request, year=None, month=None, team=None):
    if not year or not month or not team:
        raise Http404

    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(TrackingEntry.headings())
    for user in Tbluser.objects.filter(market=team):
        for entry in TrackingEntry.objects.filter(
            entry_date__year=year,
            entry_date__month=month,
            user_id=user.id):
            csvfile.writerow(entry.display_as_csv())
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=AllHolidayData_%s_%s_%s.csv' % (year, month, team)
    return response
<commit_msg>Use the datemap's MARKET_CHOICES and not the Tbluser's.<commit_after>'''
Views for the reporting functions. These will be used as endpoints when
creating the CSV reports.

Refactor this module to use a base view for CSV endpoints, which will
do the basic set up of creating a buffer object, inserting the BOM and
pushing it back to the user.
'''

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

import datetime
import csv

from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponse, Http404

from timetracker.utils.decorators import admin_check, loggedin, permissions
from timetracker.tracker.models import Tbluser, TrackingEntry
from timetracker.tracker.models import Tblauthorization as tblauth
from timetracker.utils.datemaps import (generate_select,
                                        generate_employee_box,
                                        generate_month_box,
                                        MONTH_MAP, MARKET_CHOICES)
from timetracker.utils.writers import UnicodeWriter
from timetracker.tracker.management.commands import mec_ot_report

@admin_check
def reporting(request):
    '''Base reporting hub
    Generates all the select boxes and pre-filled text fields.
    '''
    user = Tbluser.objects.get(id=request.session.get("user_id"))
    return render_to_response(
        "reporting.html",
        {
            "employee_box": generate_employee_box(user),
            "yearbox_hol": datetime.datetime.now().year,
            "monthbox_hol": generate_month_box("monthbox_hol"),
            "monthbox_ot": generate_month_box("monthbox_ot"),
            "monthbox_hr": generate_month_box("monthbox_hr"),
            "monthbox_all": generate_month_box("monthbox_all"),
            "teambox_all": generate_select(MARKET_CHOICES, id="teambox_all"),
        },
        RequestContext(request))

@admin_check
def download_all_holiday_data(request, who=None):
    '''Endpoint which creates a CSV file for all holiday data for a
    single employee.

    :param who: The ID of the person the report should be generated for
    if the user is not in the span of control for the administrator then
    no report will be generated.'''
    if not who:
        raise Http404

    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    try:
        target_user = auth_user.get_subordinates().get(id=who)
    except Tbluser.DoesNotExist:
        raise Http404

    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(TrackingEntry.headings())

    for entry in TrackingEntry.objects.filter(user_id=who):
        csvfile.writerow(entry.display_as_csv())

    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=AllHolidayData_%s.csv' % target_user.id
    return response

@admin_check
def yearmonthhol(request, year=None, month=None):
    '''Endpoint which creates a CSV file for all holiday data within
    a specific month.

    :param year: The year for the report.
    :param month: The month for the report.

    :note: Both year and mont are required.'''
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(TrackingEntry.headings())
    for user in auth_user.get_subordinates():
        for entry in TrackingEntry.objects.filter(
            entry_date__year=year,
            entry_date__month=month,
            user_id=user.id):
            csvfile.writerow(entry.display_as_csv())
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=HolidayData_%s_%s.csv' % (year, month)
    return response

@admin_check
def ot_by_month(request, year=None, month=None):
    '''Endpoint which creates a CSV file for all OT in a given month

    :param year: The year for the report.
    :param month: The month for the report.

    :note: Both year and mont are required.'''
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(
        ["Name", "Team", MONTH_MAP[int(month)-1][1]]
        )
    total_balance = 0
    for user in auth_user.get_subordinates():
        balance = user.get_total_balance(ret='flo', year=year, month=month)
        total_balance += balance
        csvfile.writerow([user.name(), user.process, "%.2f" % balance])
    csvfile.writerow(["Total", "Total", "%.2f" % total_balance])
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=OT_By_Month_%s_%s.csv' % (year, month)
    return response

@admin_check
def ot_by_year(request, year=None):
    '''Endpoint which creates a CSV file for all OT in a year.
    :param year: The year for the report.'''
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(
        ["Name", "Team"] + [MONTH_MAP[n][1] for n in range(0,12)]
        )
    total_balance = 0
    balances = {
        n: 0 for n in range(1, 13)
        }
    for user in auth_user.get_subordinates():
        row = [user.name(), user.process]
        for month in range(1, 13):
            balance = user.get_total_balance(ret='flo', year=year, month=month)
            balances[month] += balance
            row.append("%.2f" % balance if balance != 0.0 else "-")
        csvfile.writerow(row)
    totalrow = ["Total", "Total"]
    [totalrow.append("%.2f" % balances[n]) for n in range(1,13)]
    csvfile.writerow(totalrow)
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=OT_By_Year_%s_%s.csv' % (year, month)
    return response

@admin_check
def holidays_for_yearmonth(request, year=None):
    '''Endpoint which creates a CSV file for all holidays per month
    in a year

    :param year: Year for the report.'''
    if not year:
        raise Http404
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(
        ["Name"] + [MONTH_MAP[n][1] for n in range(0,12)] + ["Used", "Remaining"]
        )
    for user in auth_user.get_subordinates():
        row = [user.name()]
        total = 0
        for month in range(1,13):
            e = TrackingEntry.objects.filter(user_id=user.id,
                                             entry_date__year=year,
                                             entry_date__month=month,
                                             daytype="HOLIS").count()
            total += e
            row.append(e)
        row.append(["%d" % total, "%d" % user.holiday_balance])
        csvfile.writerow(row)
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=Holidays_for_year%s.csv' % year
    return response

@admin_check
def ot_for_hr(request, year=None, month=None):
    if not year or not month:
        raise Http404
    auth_user = Tbluser.objects.get(id=request.session.get("user_id"))
    dt = datetime.datetime(year=int(year), month=int(month), day=1)
    return mec_ot_report.report_for_account(auth_user.market, dt, send=False)

@permissions(["SUPER"])
def all_team(request, year=None, month=None, team=None):
    if not year or not month or not team:
        raise Http404

    buf = StringIO()
    buf.write("\xef\xbb\xbf")
    csvfile = UnicodeWriter(buf)
    csvfile.writerow(TrackingEntry.headings())
    for user in Tbluser.objects.filter(market=team):
        for entry in TrackingEntry.objects.filter(
            entry_date__year=year,
            entry_date__month=month,
            user_id=user.id):
            csvfile.writerow(entry.display_as_csv())
    response = HttpResponse(buf.getvalue(), mimetype="text/csv")
    response['Content-Disposition'] = \
        'attachment;filename=AllHolidayData_%s_%s_%s.csv' % (year, month, team)
    return response
<|endoftext|>


<commit_before>from django.contrib.auth.admin import (
    UserAdmin as OrigUserAdmin, PermissionDenied,
    sensitive_post_parameters_m, unquote,)
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _

from main.models import User, MultiUserUpload

@admin.register(User)
class UserAdmin(OrigUserAdmin):
    fieldsets = (
        (None,
         {'fields': ('username', 'email', 'mobile', 'student_id', 'password')}),
        (_('Personal info'),
         {'fields': ('first_name', 'last_name')}),
        (_('Permissions'),
         {'fields': ('is_active', 'is_staff', 'is_superuser',
                     'groups', 'user_permissions')}),
        (_('Important dates'),
         {'fields': ('last_login', 'date_joined')}),
    )
    readonly_fields = ('last_login', 'date_joined',)
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username', 'email', 'mobile', 'student_id',
                       'password1', 'password2'),
        }),
    )
    list_display = ('id', 'username', 'email', 'mobile', 'student_id',
                    'groups_str', 'is_staff', 'is_superuser')
    list_display_links = ('id', 'username')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('username', 'email', 'mobile', 'student_id')
    ordering = ('-is_superuser', '-is_staff')

    def groups_str(self, obj):
        return ','.join([g.name for g in obj.groups.all()])

    # 禁止普通 staff 修改（包括新增时） is_superuser 状态。
    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:  # Superuser
            return self.readonly_fields
        return self.readonly_fields + ('is_superuser',)

    # 禁止普通 staff 查看和修改 Superuser 的资料。
    def has_change_permission(self, request, obj=None):
        if obj is None:
            return True
        elif request.user.is_superuser:  # Superuser
            return True
        elif obj.is_superuser:  # obj is Superuser
            return False
        else:
            return True

    # 禁止普通 staff 修改 Superuser 的密码。
    @sensitive_post_parameters_m
    def user_change_password(self, request, id, form_url=''):
        user = self.get_object(request, unquote(id))
        if not self.has_change_permission(request, user):
            raise PermissionDenied
        return super().user_change_password(request, id, form_url)

@admin.register(MultiUserUpload)
class MultiUserUploadAdmin(admin.ModelAdmin):
    list_display = ('id', 'create_time',)
    fields = ('csv_content', 'results', 'create_time',)
    readonly_fields = ('results', 'create_time',)

    def get_readonly_fields(self, request, obj=None):
        if obj:
            return self.fields
        return self.readonly_fields
<commit_msg>admin improved.<commit_after>from django.contrib.auth.admin import (
    UserAdmin as OrigUserAdmin, PermissionDenied,
    sensitive_post_parameters_m, unquote,)
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _

from main.models import User, MultiUserUpload

@admin.register(User)
class UserAdmin(OrigUserAdmin):
    fieldsets = (
        (None,
         {'fields': ('username', 'email', 'mobile', 'student_id', 'password')}),
        (_('Personal info'),
         {'fields': ('first_name', 'last_name')}),
        (_('Permissions'),
         {'fields': ('is_active', 'is_staff', 'is_superuser',
                     'groups', 'user_permissions')}),
        (_('Important dates'),
         {'fields': ('last_login', 'date_joined')}),
    )
    readonly_fields = ('last_login', 'date_joined',)
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username', 'email', 'mobile', 'student_id',
                       'password1', 'password2'),
        }),
    )
    list_display = ('id', 'username', 'email', 'mobile', 'student_id',
                    'is_active', 'is_staff', 'groups_str', 'is_superuser')
    list_display_links = ('id', 'username')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('username', 'email', 'mobile', 'student_id')
    ordering = ('-is_active', '-is_staff', '-is_superuser')

    def groups_str(self, obj):
        return ','.join([g.name for g in obj.groups.all()])

    # 禁止普通 staff 修改（包括新增时） is_superuser 状态。
    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:  # Superuser
            return self.readonly_fields
        return self.readonly_fields + ('is_superuser',)

    # 禁止普通 staff 查看和修改 Superuser 的资料。
    def has_change_permission(self, request, obj=None):
        if obj is None:
            return True
        elif request.user.is_superuser:  # Superuser
            return True
        elif obj.is_superuser:  # obj is Superuser
            return False
        else:
            return True

    # 禁止普通 staff 修改 Superuser 的密码。
    @sensitive_post_parameters_m
    def user_change_password(self, request, id, form_url=''):
        user = self.get_object(request, unquote(id))
        if not self.has_change_permission(request, user):
            raise PermissionDenied
        return super().user_change_password(request, id, form_url)

@admin.register(MultiUserUpload)
class MultiUserUploadAdmin(admin.ModelAdmin):
    list_display = ('id', 'create_time',)
    fields = ('csv_content', 'results', 'create_time',)
    readonly_fields = ('results', 'create_time',)

    def get_readonly_fields(self, request, obj=None):
        if obj:
            return self.fields
        return self.readonly_fields
<|endoftext|>


<commit_before>from lib.search import search
from flask import Blueprint, request, Flask, make_response, request, current_app
import json
import time
import datetime
from datetime import timedelta
from functools import update_wrapper

api = Blueprint('api', __name__, template_folder='templates')
EVENT_SOURCES = ["wesleying", "wesleyanEvents"]
MENU_SOURCES = ["usdan"]

"""
http://flask.pocoo.org/snippets/56/
"""

def crossdomain(origin=None, methods=None, headers=None,
                max_age=21600, attach_to_all=True,
                automatic_options=True):
    if methods is not None:
        methods = ', '.join(sorted(x.upper() for x in methods))
    if headers is not None and not isinstance(headers, basestring):
        headers = ', '.join(x.upper() for x in headers)
    if not isinstance(origin, basestring):
        origin = ', '.join(origin)
    if isinstance(max_age, timedelta):
        max_age = max_age.total_seconds()

    def get_methods():
        if methods is not None:
            return methods

        options_resp = current_app.make_default_options_response()
        return options_resp.headers['allow']

    def decorator(f):
        def wrapped_function(*args, **kwargs):
            if automatic_options and request.method == 'OPTIONS':
                resp = current_app.make_default_options_response()
            else:
                resp = make_response(f(*args, **kwargs))
            if not attach_to_all and request.method != 'OPTIONS':
                return resp

            h = resp.headers

            h['Access-Control-Allow-Origin'] = origin
            h['Access-Control-Allow-Methods'] = get_methods()
            h['Access-Control-Max-Age'] = str(max_age)
            if headers is not None:
                h['Access-Control-Allow-Headers'] = headers
            return resp

        f.provide_automatic_options = False
        return update_wrapper(wrapped_function, f)
    return decorator

"""
TODO: Refactor some of this to get rid of the
repeated code..
"""

def err_response(errMsg):
    """
    Responds with:
    {"ERROR":errMsg}
    """
    return json.dumps({"ERROR": errMsg})

def validate_max_results_arg(req_max_results, min_results, max_results):
    try:
        req_max_results = int(req_max_results)
        errMsg = "Please specify between " + \
            str(min_results) + " and " + str(max_results) + " maxresults."
        if req_max_results < min_results:
            return [False, err_response(errMsg)]
        if req_max_results > 100:
            return [False, err_response(errMsg)]
        else:
            return [True, req_max_results]
    except:
        errMsg = ("Unable to process your request, please ensure your"
                  " request is properly formatted.")
        return [False, err_response(errMsg)]

def validate_source(req_source, accepted_sources):
    return req_source in accepted_sources

def time_from_raw_string(raw_time):
    try:
        nt = time.strptime(raw_time, "%m-%d-%Y")
        new_date = datetime.datetime(
            month=nt.tm_mon,
            day=nt.tm_mday,
            year=nt.tm_year
        )
        return new_date
    except:
        return False

def validate_search_request(request_obj, min_results, max_results):
    """
    Grabs search terms from request if they exist, including:
    -title
    -location
    -time_from
    -time_until
    -category_query
    -source
    -maxresults
    """
    raw_title = request.args.get('title')
    raw_location = request.args.get('location')
    raw_time_from = request.args.get('timeFrom')
    raw_time_until = request.args.get('timeUntil')
    raw_category = request.args.get('category')
    raw_source = request.args.get('source')
    raw_max_results = request.args.get('maxresults')

    if raw_time_from:
        raw_time_from = time_from_raw_string(raw_time_from)
        if not raw_time_from:
            return [False, err_response("Invalid: time from")]
    if raw_time_until:
        raw_time_until = time_from_raw_string(raw_time_until)
        if not raw_time_until:
            return [False, err_response("Invalid: time until")]
    if raw_max_results:
        # check the request
        valid = validate_max_results_arg(
            raw_max_results, min_results, max_results)
        if not valid[0]:
            return [False, valid[1]]
        # grab the int-ified result from validate since valid
        raw_max_results = valid[1]
    else:
        raw_max_results = max_results

    return [True, {"max_results": raw_max_results, "source": raw_source,
                   "title": raw_title, "location": raw_location,
                   "time_from": raw_time_from, "time_until": raw_time_until,
                   "category": raw_category}]

def validate_request(request_obj, min_results, max_results, accepted_sources=None):
    """
    Pulls out maxresults and source out of args.
    Validates maxresults and source, if either bad, returns
    [False, errormsg], else, [True,maxresults,source].
    If no maxresults or source in request, return defaults,
    respectively.
    Right now, source only allows for one source.
    """
    req_max_results = request.args.get('maxresults')
    raw_data_source = request.args.get('source')
    if req_max_results:
        # check the request
        valid = validate_max_results_arg(
            req_max_results, min_results, max_results)
        if not valid[0]:
            return [False, valid[1]]
        # grab the int-ified result from validate since valid
        req_max_results = valid[1]
    else:
        req_max_results = max_results

    if raw_data_source and accepted_sources:
        # validate source
        valid = validate_source(raw_data_source, accepted_sources)
        if not valid:
            source_string = ",".join(accepted_sources)
            return [False, err_response("Invalid source, please select from: " + source_string)]
        data_source = raw_data_source
    else:
        data_source = None
    return [True, {"max_results": req_max_results, "source": data_source}]

def validate_search_results(search_results):
    if not search_results:
        return json.dumps({"Result Count": 0, "Results": []})

    final_objs = format_mongo_objs(search_results)
    response = {"Result Count": len(final_objs),
                "Results": final_objs}
    return json.dumps(response)

def format_mongo_objs(mongo_objs):
    """
    Formats python datetime into isoformat for each mongo_obj
    if the obj has a time.
    Also strips out object ID
    """
    for mongo_obj in mongo_objs:
        # remove object_id
        try:
            mongo_obj.pop('_id')
        except:
            print "API: mongo_obj has no _id, panic!"

        old_time = mongo_obj.get('time')
        if not old_time:
            continue
        mongo_obj['time'] = old_time.isoformat()
    return mongo_objs

def format_status(status):
    return dict((i["name"], {"time": i["time"], "status": i["status"]}) for i in status)

"""
GENERAL ROUTES
"""

@api.route('/status', methods=['GET'])
@crossdomain(origin='*')
def get_status():
    tmp = format_mongo_objs(search.get_status())
    return json.dumps(format_status(tmp))

"""
EVENTS
"""

@api.route('/events/today', methods=['GET'])
def get_today_events():
    return get_events(True)

@api.route('/events/latest', methods=['GET'])
def get_latest_events():
    return get_events()

@api.route('/events/search', methods=['GET'])
def search_events_route():
    return search_events()

def search_events():
    """
    Moar powarful searching RAWR!
    """
    # default
    MIN_RESULTS = 1
    MAX_RESULTS = 100
    validation_result = validate_search_request(
        request, MIN_RESULTS, MAX_RESULTS)
    if not validation_result[0]:
        return validation_result[1]
    else:
        req_max_results = validation_result[1]['max_results']
        req_source = validation_result[1]['source']
        req_title = validation_result[1]['title']
        req_time_from = validation_result[1]['time_from']
        req_time_until = validation_result[1]['time_until']
        req_category = validation_result[1]['category']
        req_location = validation_result[1]['location']

    # Now search, check, and respond
    """
	def search_events(numResults,title_query,location_query,
					time_from,time_until,category_query,source):
	"""
    search_results = search.search_events(
        numResults=req_max_results,
        title_query=req_title,
        location_query=req_location,
        time_from=req_time_from,
        time_until=req_time_until,
        category_query=req_category,
        source=req_source)

    return validate_search_results(search_results)

def get_events(today=False):
    """
    Returns latest results.
    Maxresults defaults to 30.
    Max is 100
    Min is 1
    """
    # default
    MIN_RESULTS = 1
    MAX_RESULTS = 100
    accepted_sources = ["Wesleying", "Wesleyan Events"]
    validation_result = validate_request(
        request, MIN_RESULTS, MAX_RESULTS, accepted_sources)
    if not validation_result[0]:
        return validation_result[1]
    else:
        req_max_results = validation_result[1]['max_results']
        req_source = validation_result[1]['source']

    # Now search, check, and respond
    if today:
        search_results = search.get_events_today()
    else:
        search_results = search.get_events(req_max_results, req_source)

    return validate_search_results(search_results)

@api.route('/events/sources', methods=['GET'])
def get_sources():
    return json.dumps(EVENT_SOURCES)

"""
MENUS

/latest --gets everything, default maxresults = 7 days,
			default both usdan and summerfields
/today --gets everything, default both usdan and summerfields
"""

def get_menus(min_res, max_res, today=False):
    # default
    MIN_RESULTS = min_res
    MAX_RESULTS = max_res

    validation_result = validate_request(request, MIN_RESULTS, MAX_RESULTS)
    if not validation_result[0]:
        return validation_result[1]
    else:
        req_max_results = validation_result[1]['max_results']

    # Now search, check, and respond
    if today:
        search_results = search.get_menus_today()
    else:
        search_results = search.get_menus_all(req_max_results)
    # only need to validate the usdan ones.
    usdan_results = search_results.get('usdan')
    if not usdan_results:
        return json.dumps({"Result Count": 0, "Results": []})

    # get late night and summerfields.
    # this method will return all or nothing,
    # so if usdan fails, you get nothing.
    # ALSO, number of results is tied to usdan results
    summerfields = format_mongo_objs(search_results.get('summerfields'))
    late_night = format_mongo_objs(search_results.get('late_night'))
    usdan = format_mongo_objs(usdan_results)
    final_objs = {"usdan": usdan,
                  "summerfields": summerfields,
                  "late_night": late_night}

    response = {"Result Count": len(usdan),
                "Results": final_objs}
    return json.dumps(response)

@api.route('/menus/latest', methods=['GET'])
def get_menus_all():
    return get_menus(1, 100)

@api.route('/menus/today', methods=['GET'])
def get_menus_today():
    """
    Only source argument accepted here.
    """
    return get_menus(1, 1, True)

"""
FILM SERIES METHODS
"""

def get_film_series(today=False):
    if today:
        search_results = search.get_film_series_today()
    else:
        search_results = search.get_film_series_all()
    # only need to validate the usdan ones.
    return validate_search_results(search_results)

@api.route('/filmseries/all')
def get_film_series_all():
    return get_film_series()

@api.route('/filmseries/today')
def get_film_series_today():
    return get_film_series(True)
<commit_msg>Added the rest of the cross domain allowances<commit_after>from lib.search import search
from flask import Blueprint, make_response, request, current_app
import json
import time
import datetime
from datetime import timedelta
from functools import update_wrapper

api = Blueprint('api', __name__, template_folder='templates')
EVENT_SOURCES = ["wesleying", "wesleyanEvents"]
MENU_SOURCES = ["usdan"]

"""
http://flask.pocoo.org/snippets/56/
"""

def crossdomain(origin=None, methods=None, headers=None,
                max_age=21600, attach_to_all=True,
                automatic_options=True):
    if methods is not None:
        methods = ', '.join(sorted(x.upper() for x in methods))
    if headers is not None and not isinstance(headers, basestring):
        headers = ', '.join(x.upper() for x in headers)
    if not isinstance(origin, basestring):
        origin = ', '.join(origin)
    if isinstance(max_age, timedelta):
        max_age = max_age.total_seconds()

    def get_methods():
        if methods is not None:
            return methods

        options_resp = current_app.make_default_options_response()
        return options_resp.headers['allow']

    def decorator(f):
        def wrapped_function(*args, **kwargs):
            if automatic_options and request.method == 'OPTIONS':
                resp = current_app.make_default_options_response()
            else:
                resp = make_response(f(*args, **kwargs))
            if not attach_to_all and request.method != 'OPTIONS':
                return resp

            h = resp.headers

            h['Access-Control-Allow-Origin'] = origin
            h['Access-Control-Allow-Methods'] = get_methods()
            h['Access-Control-Max-Age'] = str(max_age)
            if headers is not None:
                h['Access-Control-Allow-Headers'] = headers
            return resp

        f.provide_automatic_options = False
        return update_wrapper(wrapped_function, f)
    return decorator

"""
TODO: Refactor some of this to get rid of the
repeated code..
"""

def err_response(errMsg):
    """
    Responds with:
    {"ERROR":errMsg}
    """
    return json.dumps({"ERROR": errMsg})

def validate_max_results_arg(req_max_results, min_results, max_results):
    try:
        req_max_results = int(req_max_results)
        errMsg = "Please specify between " + \
            str(min_results) + " and " + str(max_results) + " maxresults."
        if req_max_results < min_results:
            return [False, err_response(errMsg)]
        if req_max_results > 100:
            return [False, err_response(errMsg)]
        else:
            return [True, req_max_results]
    except:
        errMsg = ("Unable to process your request, please ensure your"
                  " request is properly formatted.")
        return [False, err_response(errMsg)]

def validate_source(req_source, accepted_sources):
    return req_source in accepted_sources

def time_from_raw_string(raw_time):
    try:
        nt = time.strptime(raw_time, "%m-%d-%Y")
        new_date = datetime.datetime(
            month=nt.tm_mon,
            day=nt.tm_mday,
            year=nt.tm_year
        )
        return new_date
    except:
        return False

def validate_search_request(request_obj, min_results, max_results):
    """
    Grabs search terms from request if they exist, including:
    -title
    -location
    -time_from
    -time_until
    -category_query
    -source
    -maxresults
    """
    raw_title = request.args.get('title')
    raw_location = request.args.get('location')
    raw_time_from = request.args.get('timeFrom')
    raw_time_until = request.args.get('timeUntil')
    raw_category = request.args.get('category')
    raw_source = request.args.get('source')
    raw_max_results = request.args.get('maxresults')

    if raw_time_from:
        raw_time_from = time_from_raw_string(raw_time_from)
        if not raw_time_from:
            return [False, err_response("Invalid: time from")]
    if raw_time_until:
        raw_time_until = time_from_raw_string(raw_time_until)
        if not raw_time_until:
            return [False, err_response("Invalid: time until")]
    if raw_max_results:
        # check the request
        valid = validate_max_results_arg(
            raw_max_results, min_results, max_results)
        if not valid[0]:
            return [False, valid[1]]
        # grab the int-ified result from validate since valid
        raw_max_results = valid[1]
    else:
        raw_max_results = max_results

    return [True, {"max_results": raw_max_results, "source": raw_source,
                   "title": raw_title, "location": raw_location,
                   "time_from": raw_time_from, "time_until": raw_time_until,
                   "category": raw_category}]

def validate_request(request_obj, min_results, max_results, accepted_sources=None):
    """
    Pulls out maxresults and source out of args.
    Validates maxresults and source, if either bad, returns
    [False, errormsg], else, [True,maxresults,source].
    If no maxresults or source in request, return defaults,
    respectively.
    Right now, source only allows for one source.
    """
    req_max_results = request.args.get('maxresults')
    raw_data_source = request.args.get('source')
    if req_max_results:
        # check the request
        valid = validate_max_results_arg(
            req_max_results, min_results, max_results)
        if not valid[0]:
            return [False, valid[1]]
        # grab the int-ified result from validate since valid
        req_max_results = valid[1]
    else:
        req_max_results = max_results

    if raw_data_source and accepted_sources:
        # validate source
        valid = validate_source(raw_data_source, accepted_sources)
        if not valid:
            source_string = ",".join(accepted_sources)
            return [False, err_response("Invalid source, please select from: " + source_string)]
        data_source = raw_data_source
    else:
        data_source = None
    return [True, {"max_results": req_max_results, "source": data_source}]

def validate_search_results(search_results):
    if not search_results:
        return json.dumps({"Result Count": 0, "Results": []})

    final_objs = format_mongo_objs(search_results)
    response = {"Result Count": len(final_objs),
                "Results": final_objs}
    return json.dumps(response)

def format_mongo_objs(mongo_objs):
    """
    Formats python datetime into isoformat for each mongo_obj
    if the obj has a time.
    Also strips out object ID
    """
    for mongo_obj in mongo_objs:
        # remove object_id
        try:
            mongo_obj.pop('_id')
        except:
            print "API: mongo_obj has no _id, panic!"

        old_time = mongo_obj.get('time')
        if not old_time:
            continue
        mongo_obj['time'] = old_time.isoformat()
    return mongo_objs

def format_status(status):
    return dict((i["name"], {"time": i["time"], "status": i["status"]}) for i in status)

"""
GENERAL ROUTES
"""

@api.route('/status', methods=['GET'])
@crossdomain(origin='*')
def get_status():
    tmp = format_mongo_objs(search.get_status())
    return json.dumps(format_status(tmp))

"""
EVENTS
"""

@api.route('/events/today', methods=['GET'])
@crossdomain(origin='*')
def get_today_events():
    return get_events(True)

@api.route('/events/latest', methods=['GET'])
@crossdomain(origin='*')
def get_latest_events():
    return get_events()

@api.route('/events/search', methods=['GET'])
@crossdomain(origin='*')
def search_events_route():
    return search_events()

def search_events():
    """
    Moar powarful searching RAWR!
    """
    # default
    MIN_RESULTS = 1
    MAX_RESULTS = 100
    validation_result = validate_search_request(
        request, MIN_RESULTS, MAX_RESULTS)
    if not validation_result[0]:
        return validation_result[1]
    else:
        req_max_results = validation_result[1]['max_results']
        req_source = validation_result[1]['source']
        req_title = validation_result[1]['title']
        req_time_from = validation_result[1]['time_from']
        req_time_until = validation_result[1]['time_until']
        req_category = validation_result[1]['category']
        req_location = validation_result[1]['location']

    # Now search, check, and respond
    """
	def search_events(numResults,title_query,location_query,
					time_from,time_until,category_query,source):
	"""
    search_results = search.search_events(
        numResults=req_max_results,
        title_query=req_title,
        location_query=req_location,
        time_from=req_time_from,
        time_until=req_time_until,
        category_query=req_category,
        source=req_source)

    return validate_search_results(search_results)

def get_events(today=False):
    """
    Returns latest results.
    Maxresults defaults to 30.
    Max is 100
    Min is 1
    """
    # default
    MIN_RESULTS = 1
    MAX_RESULTS = 100
    accepted_sources = ["Wesleying", "Wesleyan Events"]
    validation_result = validate_request(
        request, MIN_RESULTS, MAX_RESULTS, accepted_sources)
    if not validation_result[0]:
        return validation_result[1]
    else:
        req_max_results = validation_result[1]['max_results']
        req_source = validation_result[1]['source']

    # Now search, check, and respond
    if today:
        search_results = search.get_events_today()
    else:
        search_results = search.get_events(req_max_results, req_source)

    return validate_search_results(search_results)

@api.route('/events/sources', methods=['GET'])
@crossdomain(origin='*')
def get_sources():
    return json.dumps(EVENT_SOURCES)

"""
MENUS

/latest --gets everything, default maxresults = 7 days,
			default both usdan and summerfields
/today --gets everything, default both usdan and summerfields
"""

def get_menus(min_res, max_res, today=False):
    # default
    MIN_RESULTS = min_res
    MAX_RESULTS = max_res

    validation_result = validate_request(request, MIN_RESULTS, MAX_RESULTS)
    if not validation_result[0]:
        return validation_result[1]
    else:
        req_max_results = validation_result[1]['max_results']

    # Now search, check, and respond
    if today:
        search_results = search.get_menus_today()
    else:
        search_results = search.get_menus_all(req_max_results)
    # only need to validate the usdan ones.
    usdan_results = search_results.get('usdan')
    if not usdan_results:
        return json.dumps({"Result Count": 0, "Results": []})

    # get late night and summerfields.
    # this method will return all or nothing,
    # so if usdan fails, you get nothing.
    # ALSO, number of results is tied to usdan results
    summerfields = format_mongo_objs(search_results.get('summerfields'))
    late_night = format_mongo_objs(search_results.get('late_night'))
    usdan = format_mongo_objs(usdan_results)
    final_objs = {"usdan": usdan,
                  "summerfields": summerfields,
                  "late_night": late_night}

    response = {"Result Count": len(usdan),
                "Results": final_objs}
    return json.dumps(response)

@api.route('/menus/latest', methods=['GET'])
@crossdomain(origin='*')
def get_menus_all():
    return get_menus(1, 100)

@api.route('/menus/today', methods=['GET'])
def get_menus_today():
    """
    Only source argument accepted here.
    """
    return get_menus(1, 1, True)

"""
FILM SERIES METHODS
"""

def get_film_series(today=False):
    if today:
        search_results = search.get_film_series_today()
    else:
        search_results = search.get_film_series_all()
    # only need to validate the usdan ones.
    return validate_search_results(search_results)

@api.route('/filmseries/all')
@crossdomain(origin='*')
def get_film_series_all():
    return get_film_series()

@api.route('/filmseries/today')
@crossdomain(origin='*')
def get_film_series_today():
    return get_film_series(True)
<|endoftext|>


<commit_before>import datetime
import os.path
import re
import sys
import StringIO
import xml.dom.minidom

import PyRSS2Gen

import helpers

video_format = "mp4"

# Video format params
vfp = { 'mp4': {'vyd' : 'mp4', # Youtube downloader
                'vfn':'mp4', # Filename extension
                'vmt':'video/mp4', # MIME type
                'vdr': 'mp4',  # Directory
                'vcn': 'MPEG Video' # Video codec name
                }
        }

print sys.argv[1]
tree = helpers.load_xml_course(sys.argv[1])

items = []

valid_youtube_id = re.compile("^[0-9a-zA-Z_\-]*$")

## Add videos
for e in tree.iter():
    item_dict = dict()
    if e.tag in ['video']:
        if 'youtube_id_1_0' not in e.attrib:
            continue
        item_dict['title'] = e.attrib['display_name']
        item_dict['guid'] = e.attrib['url_name']
        youtube_id = e.attrib['youtube_id_1_0']
        if not valid_youtube_id.match(youtube_id):
            raise TypeError("Youtube ID has an invalid string. Security issue?")
        item_dict['link'] = "https://www.youtube.com/watch?v="+youtube_id
        description = list()
        node = e
        while node != None:
            if 'display_name' in node.attrib:
                description.append(node.attrib['display_name'])
            node = node.parent
        description.reverse()

        item_dict['description'] = "edX RSS Prototype. Video is from "+(" / ".join(description))

        dl_filename = os.path.join('output', youtube_id+"."+vfp[video_format]['vfn'])
        if not os.path.exists(dl_filename):
            command = "youtube-dl -f {fmt} https://www.youtube.com/watch?v={uid} -o {file}".format(fmt=vfp[video_format]['vyd'],
                                                                                uid=youtube_id,
                                                                                file=dl_filename)
            os.system(command)
        #video_url = "http://foo.com"
        #item_dict['enclosure'] = video_url
        #print e.attrib['youtube_id_1_0']
        #print
        items.append(PyRSS2Gen.RSSItem(**item_dict))

rss = PyRSS2Gen.RSS2(
    title = "edX Course",
    link = "http://www.edx.org",
    description = "A prototype podcast of the videos from {coursename}, a course from {org} on edX. The full course, including assessments, is available, free-of-charge, at http://www.edx.org/".format(coursename=tree.getroot().attrib['display_name'], org=tree.getroot().attrib['org']),
    lastBuildDate = datetime.datetime.now(),
    items = items,
    managingEditor = "edX Learning Sciences"
    )

## Write output to a file
data = StringIO.StringIO()
rss.write_xml(data)
f = open("output/course.rss", "w")
f.write(xml.dom.minidom.parseString(data.getvalue()).toprettyxml())
f.close()
<commit_msg>More meaningful fields<commit_after>import StringIO
import argparse
import datetime
import os
import os.path
import re
import sys
import urlparse
import xml.dom.minidom

import PyRSS2Gen

import helpers

parser = argparse.ArgumentParser(description = "Generate an RSS feed of a course.")
parser.add_argument("base", help="Base directory of Studio-dumped XML")
parser.add_argument("url_base", help="URL the feed will be hosted from")
parser.add_argument("--format", help="Format of RSS feed (mp4, webm, 3gp, or m4a)", default='webm', dest='format')
parser.add_argument("--course_url", help="URL of the course about page", default="https://www.edx.org/", dest="course_url")

args = parser.parse_args()

video_format = args.format
url_base = args.url_base
base = args.base

# Video format params
vfp = { 'mp4': {'vyd' : 'mp4', # Youtube downloader
                'vfn':'mp4', # Filename extension
                'vmt':'video/mp4', # MIME type
                'vdr': 'mp4',  # Directory
                'vcn': 'MPEG Video', # Video codec name
                'vdc': 'This RSS feed is for MPEG videos. This is the most common video format and should work with most software. ' # Description
                },
        'webm': {'vyd' : 'webm', # Youtube downloader
                'vfn':'webm', # Filename extension
                'vmt':'video/webm', # MIME type
                'vdr': 'webm',  # Directory
                'vcn': 'WebM Video', # Video codec name
                'vdc': 'This RSS feed is using WebM videos. WebM is an advanced video format developed by Goolgle. This is the recommended feed if your software supports it (most software does not). ' # Description
                },
        '3gp': {'vyd' : '3gp', # Youtube downloader
                'vfn':'3gp', # Filename extension
                'vmt':'video/3gpp', # MIME type
                'vdr': '3gp',  # Directory
                'vcn': '3GPP Video', # Video codec name
                'vdc': 'This RSS feed is for video files in the 3gpp format. 3gpp is a low-bandwidth format commonly used for video delivered to cell phones. ' # Description
                },
        'm4a': {'vyd' : 'm4a', # Youtube downloader
                'vfn':'m4a', # Filename extension
                'vmt':'audio/mp4a-latm', # MIME type
                'vdr': 'm4a',  # Directory
                'vcn': 'AAC Audio', # Video codec name
                'vdc': 'This is an audio-only RSS feed. It uses the AAC audio codec. ' # Description
                },
        }

print base
tree = helpers.load_xml_course(base)

items = []

valid_youtube_id = re.compile("^[0-9a-zA-Z_\-]*$")

## Add videos
for e in tree.iter():
    item_dict = dict()
    if e.tag in ['video']:
        if 'youtube_id_1_0' not in e.attrib:
            continue
        item_dict['title'] = e.attrib['display_name']
        item_dict['guid'] = e.attrib['url_name']
        youtube_id = e.attrib['youtube_id_1_0']
        if not valid_youtube_id.match(youtube_id):
            raise TypeError("Youtube ID has an invalid string. Security issue?")
        item_dict['link'] = "https://www.youtube.com/watch?v="+youtube_id
        description = list()
        node = e
        while node != None:
            if 'display_name' in node.attrib:
                description.append(node.attrib['display_name'])
            node = node.parent
        description.reverse()

        item_dict['description'] = "edX RSS Prototype. Video is from "+(" / ".join(description))

        base_filename = youtube_id+"."+vfp[video_format]['vfn']
        dl_filename = os.path.join('output', base_filename)
        if not os.path.exists(dl_filename):
            command = "youtube-dl -f {fmt} https://www.youtube.com/watch?v={uid} -o {file}".format(fmt=vfp[video_format]['vyd'],
                                                                                uid=youtube_id,
                                                                                file=dl_filename)
            os.system(command)
        item_dict['enclosure'] = PyRSS2Gen.Enclosure(url=urlparse.urljoin(url_base, base_filename),
                                                     length=os.stat(dl_filename).st_size,
                                                     type=vfp[video_format]['vmt'])
        #video_url = "http://foo.com"
        #item_dict['enclosure'] = video_url
        #print e.attrib['youtube_id_1_0']
        #print
        items.append(PyRSS2Gen.RSSItem(**item_dict))

print tree.getroot().attrib

rss = PyRSS2Gen.RSS2(
    title = tree.getroot().attrib['display_name'],
    link = args.course_url,
    description = "A prototype podcast of the videos from {coursename}, a course from {org} on edX. The full course, including assessments, is available, free-of-charge, at {course_url}.".format(coursename=tree.getroot().attrib['display_name'], org=tree.getroot().attrib['org'], course_url = args.course_url),
    lastBuildDate = datetime.datetime.now(),
    items = items,
    managingEditor = "edX Learning Sciences"
    )

## Write output to a file
data = StringIO.StringIO()
rss.write_xml(data)
f = open("output/course.rss", "w")
f.write(xml.dom.minidom.parseString(data.getvalue()).toprettyxml())
f.close()
<|endoftext|>


<commit_before>#!venv/bin/python3
# media_namer.py - Renames passed media files in a folder (and subfolders) using
#                   OMDB for movies and theTVDB for TV shows

import sys
import os
import logging
import argparse
import random
import subprocess
import shlex

import videoLister
import mff

def main():
    # set up logging
    logger = logging.getLogger(__name__)
    logging.basicConfig(filename='whats_on_tv.log', filemode='w', level=logging.INFO,
                        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    # set up argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('input', help='Input directory to search.')
    # Set up range of choices starting at 15 min in 15 min increments up to 300 min
    parser.add_argument('-t', '--time', help='Viewing time in minutes', type=int, choices=range(15,301)[::15])
    parser.add_argument('-n', '--num', help='Number of videos to queue', type=int, default=1)
    args = parser.parse_args()
    if args.input:
        in_path = args.input
    else:
        parser.print_help()
        sys.exit(1)

    # One time loop to generate a list of available media files in path
    m_list = []
    for item in videoLister.videoDir(in_path):
        logger.debug("Found: {}".format(item)) # Can write really long log files
        m_list.append(item)

    # Randomly select a video to play
    p_list = []
    for x in range(args.num):
        if args.time: # Find a file with a duration shorter than allotted time
            duration = 999 # Fix this, its hacky to get the loop to run...
            while duration > args.time:
                choice = random.choice(m_list)
                m_list.remove(choice) # remove the choice from the list
                m_file = mff.format_info(choice) # get file details
                duration = round(float(m_file['duration']) / 60) # convert to integer minutes
                logger.info("Selected: {}".format(os.path.basename(choice)))
                logger.info("Running time: {} min".format(duration))
        else:
            choice = random.choice(m_list)
            logger.info("Selected: {}".format(os.path.basename(choice)))

        logger.info("Added to playlist: {}".format(os.path.basename(choice)))
        p_list.append(choice)

    logger.info("Playlist: {}".format(p_list))

    # Launch selected video with MPV in full screen
    play_command = 'mpv {} --really-quiet --fs &'.format(' '.join('"{}"'.format(p) for p in p_list))
    proc = subprocess.Popen(shlex.split(play_command))
    # use proc.terminate() to kill

if __name__ == '__main__':
    main()
<commit_msg>switch to default duration, clean up<commit_after>#!venv/bin/python3
# media_namer.py - Renames passed media files in a folder (and subfolders) using
#                   OMDB for movies and theTVDB for TV shows

import sys
import os
import logging
import argparse
import random
import subprocess
import shlex

import videoLister
import mff

def main():
    # set up logging
    logger = logging.getLogger(__name__)
    logging.basicConfig(filename='whats_on_tv.log', filemode='w', level=logging.INFO,
                        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    # set up argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('input', help='Input directory to search.')
    # Set up range of choices starting at 15 min in 15 min increments up to 300 min
    parser.add_argument('-t', '--time', help='Viewing time in minutes', type=int, choices=range(15,301)[::15], default=300)
    parser.add_argument('-n', '--num', help='Number of videos to queue', type=int, default=1)
    args = parser.parse_args()
    if args.input:
        in_path = args.input
    else:
        parser.print_help()
        sys.exit(1)

    # One time loop to generate a list of available media files in path
    m_list = []
    for item in videoLister.videoDir(in_path):
        logger.debug("Found: {}".format(item)) # Can write really long log files
        m_list.append(item)

    # Randomly select a video to play
    p_list = []
    for x in range(args.num):
        duration = 999 # Fix this, its hacky to get the loop to run...
        while duration > args.time: # Find a file with a duration shorter than allotted time
            choice = random.choice(m_list)
            m_list.remove(choice) # remove the choice from the list
            m_file = mff.format_info(choice) # get file details
            duration = round(float(m_file['duration']) / 60) # convert to integer minutes
            logger.info("Selected: {}".format(os.path.basename(choice)))
            logger.info("Running time: {} min".format(duration))

        logger.info("Added to playlist: {}".format(os.path.basename(choice)))
        p_list.append(choice)

    logger.info("Playlist: {}".format(p_list))

    # Launch selected video with MPV in full screen
    play_command = 'mpv {} --really-quiet --fs &'.format(' '.join('"{}"'.format(p) for p in p_list))
    proc = subprocess.Popen(shlex.split(play_command))
    # use proc.terminate() to kill

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


<commit_before><commit_msg>Add Mark property to ArchRebar object<commit_after><|endoftext|>


<commit_before>"""Create an emoji font from a set of SVGs.

UFO handling informed by Cosimo's
https://gist.github.com/anthrotype/2acbc67c75d6fa5833789ec01366a517

For COLR:
    Each SVG file represent one base glyph in the COLR font.
    For each glyph, we get the list of layers associated with it.
    For each layer we have a tuple (Paint, SVGPath).
    To convert to font format we  use the UFO Glyph pen.

Sample usage:
make_emoji_font.py -v 1 $(find ~/oss/noto-emoji/svg -name '*.svg')
make_emoji_font.py $(find ~/oss/twemoji/assets/svg -name '*.svg')
"""
from absl import app
from absl import flags
from absl import logging
import collections
from color_glyph import ColorGlyph
from fontTools import ttLib
from fontTools.pens.transformPen import TransformPen
import io
from itertools import chain
from nanosvg.svg import SVG
from nanosvg.svg_pathops import skia_path
import os
import regex
import sys
import ufoLib2
import ufo2ft

ColorFontConfig = collections.namedtuple(
    'ColorFontConfig', ['upem', 'family', 'color_format', 'output_format'])

# A color font generator.
#   apply_ufo(ufo, color_glyphs) is called first, to update a generated UFO
#   apply_ttfont(ufo, color_glyphs, ttfont) is called second, to allow fixups after ufo2ft
# Ideally we delete the ttfont stp in future. Blocking issues:
#   https://github.com/unified-font-object/ufo-spec/issues/104
# If the output file is .ufo then apply_ttfont is not called.
# Where possible code to the ufo and let apply_ttfont be a nop.
ColorGenerator = collections.namedtuple('ColorGenerator', ['apply_ufo', 'apply_ttfont'])

_COLOR_FORMAT_GENERATORS = {
    'colr_0': ColorGenerator(lambda *args: _colr_ufo(0, *args),
                             lambda *_: None),
    'colr_1': ColorGenerator(lambda *args: _colr_ufo(1, *args),
                             lambda *_: None),
    'svg': ColorGenerator(lambda *_: None,
                          lambda *args: _svg_ttfont(*args, zip=False)),
    'svgz': ColorGenerator(lambda *_: None,
                           lambda *args: _svg_ttfont(*args, zip=True)),
    'cbdt': ColorGenerator(lambda *args: _not_impl('ufo', *args),
                           lambda *args: _not_impl('TTFont', *args)),
    'sbix': ColorGenerator(lambda *args: _not_impl('ufo', *args),
                           lambda *args: _not_impl('TTFont', *args)),
}

FLAGS = flags.FLAGS

# TODO move to config file?
flags.DEFINE_integer('upem', 1024, 'Units per em.')
flags.DEFINE_string('family', 'An Emoji Family', 'Family name.')
flags.DEFINE_enum('color_format', 'colr_0',
                  sorted(_COLOR_FORMAT_GENERATORS.keys()),
                  'Type of color font to generate.')
flags.DEFINE_string('output_file', '/tmp/AnEmojiFamily-Regular.ttf',
                    'Dest file, can be .ttf, .otf, or .ufo')

def _codepoints_from_filename(filename):
    match = regex.search(r'(?:[-_]?([0-9a-fA-F]{4,}))+', filename)
    if match:
        return tuple(int(s, 16) for s in match.captures(1))
    logging.warning(f'Bad filename {filename}; unable to extract codepoints')
    return None

def _nanosvg(filename):
    try:
        return SVG.parse(filename).tonanosvg()
    except Exception as e:
        logging.warning(f'{filename} failed: {e}')
    return None

def _inputs(filenames):
    for filename in filenames:
        codepoints = _codepoints_from_filename(filename)
        nanosvg = _nanosvg(filename)
        if codepoints and nanosvg:
            yield (filename, codepoints, nanosvg)

def _ufo(family, upem):
    ufo = ufoLib2.Font()
    ufo.info.familyName = family
    # set various font metadata; see the full list of fontinfo attributes at
    # http://unifiedfontobject.org/versions/ufo3/fontinfo.plist/
    ufo.info.unitsPerEm = upem

    # Must have .notdef and Win 10 Chrome likes a blank gid1 so make that space
    ufo.newGlyph('.notdef')
    space = ufo.newGlyph('.space')
    space.unicodes = [0x0020]
    space.width = upem
    ufo.glyphOrder = ['.notdef', '.space']

    return ufo

def _layer(ufo, idx):
    """UFO has a global set of layers.

    Each layer then has glyphs. For an N-layer COLR glyph we
    write the glyph into global layers 0..N-1. The UFO will end up with
    as many global layers as the "deepest" glyph.

    The only real significance is z-order so name on that basis.
    """
    name = f'z_{idx}'
    if name not in ufo.layers:
        ufo.newLayer(name)
    return ufo.layers[name]

def _make_ttfont(config, ufo, color_glyphs):
    if config.output_format == '.ufo':
        return None

    # Use skia-pathops to remove overlaps (i.e. simplify self-overlapping
    # paths) because the default ("booleanOperations") does not support
    # quadratic bezier curves (qcurve), which may appear
    # when we pass through nanosvg (e.g. arcs or stroked paths).
    ttfont = None
    if config.output_format == ".ttf":
        ttfont = ufo2ft.compileTTF(ufo, overlapsBackend="pathops")
    if config.output_format == ".otf":
        ttfont = ufo2ft.compileOTF(ufo, overlapsBackend="pathops")

    if not ttfont:
        raise ValueError(f'Unable to generate {color_format} {dest_format}')

    # Permit fixups where we can't express something adequately in UFO
    _COLOR_FORMAT_GENERATORS[config.color_format].apply_ttfont(ufo, color_glyphs, ttfont)

    return ttfont

def _write(ufo, ttfont, output_file):
    logging.info('Writing %s', output_file)

    if os.path.splitext(output_file)[1] == ".ufo":
        ufo.save(output_file, overwrite=True)
    else:
        ttfont.save(output_file)

def _not_impl(*_):
    raise NotImplementedError('%s not implemented' % FLAGS.color_format)

def _colr_ufo(colr_version, ufo, color_glyphs):
    # Sort colors so the index into colors == index into CPAL palette
    colors = sorted(set(chain.from_iterable((g.colors() for g in color_glyphs))))
    logging.debug('colors %s', colors)

    # KISS; use a single global palette
    ufo.lib[ufo2ft.constants.COLOR_PALETTES_KEY] = [[c.to_ufo_color() for c in colors]]

    # We created glyph_name on the default layer for the base glyph
    # Now create glyph_name on layers 0..N-1 for the colored layers
    for color_glyph in color_glyphs:
        # For COLRv0, paint is just the palette index
        # For COLRv1, it's a data structure describing paint
        layer_to_paint = []
        svg_units_to_font_units = color_glyph.transform_for_font_space()
        for idx, (paint, path) in enumerate(color_glyph.as_painted_layers()):
            glyph_layer = _layer(ufo, idx)

            if colr_version == 0:
                # COLRv0: draw using the first available color on the glyph_layer
                # Results for gradients will be suboptimal :)
                color = next(paint.colors())
                layer_to_paint.append((glyph_layer.name, colors.index(color)))

            elif colr_version == 1:
                # COLRv0: fill in gradient paint structures
                layer_to_paint.append((glyph_layer.name, paint.to_ufo_paint(colors)))

            else:
                raise ValueError(f'Unsupported COLR version: {colr_version}')

            # we've got a colored layer, put a glyph on it
            glyph = glyph_layer.newGlyph(color_glyph.glyph_name)
            glyph.width = ufo.info.unitsPerEm

            pen = TransformPen(glyph.getPen(), svg_units_to_font_units)
            skia_path(path).draw(pen)

        # each base glyph contains a list of (layer.name, paint info) in z-order
        base_glyph = ufo.get(color_glyph.glyph_name)
        base_glyph.lib[ufo2ft.constants.COLOR_LAYER_MAPPING_KEY] = layer_to_paint

    # Magic Incantation.
    # the filter below is required to enable the copying of the color layers
    # to standalone glyphs in the default glyph set used to build the TTFont
    # TODO(anthrotype) Make this automatic somehow?
    ufo.lib[ufo2ft.constants.FILTERS_KEY] = [
        {"name": "Explode Color Layer Glyphs", "pre": True}
    ]

def _svg_ttfont(ufo, color_glyphs, ttfont, zip=False):
    svg_table = ttLib.newTable('SVG ')
    svg_table.compressed = zip
    svg_table.docList = [(c.nsvg
                          # dumb sizing isn't useful
                          .remove_attributes(('width', 'height'))
                          # Firefox likes to render blank if present
                          .remove_attributes(('enable-background',))
                          # Required to match gid
                          .set_attributes((('id', f'glyph{c.glyph_id}'),))
                          .tostring(),
                          ttfont.getGlyphID(c.glyph_name),
                          ttfont.getGlyphID(c.glyph_name))
                         for c in color_glyphs]
    ttfont[svg_table.tableTag] = svg_table

def _generate_color_font(config, glyph_inputs):
    """Make a UFO and optionally a TTFont from svgs.

    Args:
        color_font_config: ColorFontConfig
        glyph_inputs: sequence of (filename, codepoints, nanosvg) tuples
    """
    ufo = _ufo(config.family, config.upem)
    base_gid = len(ufo.glyphOrder)
    color_glyphs = [ColorGlyph.create(ufo, filename, base_gid + idx, codepoints, nsvg)
                    for idx, (filename, codepoints, nsvg) in enumerate(glyph_inputs)]
    ufo.glyphOrder = ufo.glyphOrder + [g.glyph_name for g in color_glyphs]
    for g in color_glyphs:
        assert g.glyph_id == ufo.glyphOrder.index(g.glyph_name)

    _COLOR_FORMAT_GENERATORS[config.color_format].apply_ufo(ufo, color_glyphs)

    ttfont = _make_ttfont(config, ufo, color_glyphs)

    # TODO may wish to nuke 'post' glyph names

    return ufo, ttfont

def main(argv):
    config = ColorFontConfig(upem=FLAGS.upem,
                             family=FLAGS.family,
                             color_format=FLAGS.color_format,
                             output_format=os.path.splitext(FLAGS.output_file)[1])

    inputs = list(_inputs(argv[1:]))
    logging.info(f'{len(inputs)}/{len(argv[1:])} inputs prepared successfully')

    ufo, ttfont = _generate_color_font(config, inputs)

    _write(ufo, ttfont, FLAGS.output_file)
    logging.info('Wrote %s' % FLAGS.output_file)

if __name__ == "__main__":
    app.run(main)
<commit_msg>remove magic incantation no longer required with latest ufo2ft<commit_after>"""Create an emoji font from a set of SVGs.

UFO handling informed by Cosimo's
https://gist.github.com/anthrotype/2acbc67c75d6fa5833789ec01366a517

For COLR:
    Each SVG file represent one base glyph in the COLR font.
    For each glyph, we get the list of layers associated with it.
    For each layer we have a tuple (Paint, SVGPath).
    To convert to font format we  use the UFO Glyph pen.

Sample usage:
make_emoji_font.py -v 1 $(find ~/oss/noto-emoji/svg -name '*.svg')
make_emoji_font.py $(find ~/oss/twemoji/assets/svg -name '*.svg')
"""
from absl import app
from absl import flags
from absl import logging
import collections
from color_glyph import ColorGlyph
from fontTools import ttLib
from fontTools.pens.transformPen import TransformPen
import io
from itertools import chain
from nanosvg.svg import SVG
from nanosvg.svg_pathops import skia_path
import os
import regex
import sys
import ufoLib2
import ufo2ft

ColorFontConfig = collections.namedtuple(
    'ColorFontConfig', ['upem', 'family', 'color_format', 'output_format'])

# A color font generator.
#   apply_ufo(ufo, color_glyphs) is called first, to update a generated UFO
#   apply_ttfont(ufo, color_glyphs, ttfont) is called second, to allow fixups after ufo2ft
# Ideally we delete the ttfont stp in future. Blocking issues:
#   https://github.com/unified-font-object/ufo-spec/issues/104
# If the output file is .ufo then apply_ttfont is not called.
# Where possible code to the ufo and let apply_ttfont be a nop.
ColorGenerator = collections.namedtuple('ColorGenerator', ['apply_ufo', 'apply_ttfont'])

_COLOR_FORMAT_GENERATORS = {
    'colr_0': ColorGenerator(lambda *args: _colr_ufo(0, *args),
                             lambda *_: None),
    'colr_1': ColorGenerator(lambda *args: _colr_ufo(1, *args),
                             lambda *_: None),
    'svg': ColorGenerator(lambda *_: None,
                          lambda *args: _svg_ttfont(*args, zip=False)),
    'svgz': ColorGenerator(lambda *_: None,
                           lambda *args: _svg_ttfont(*args, zip=True)),
    'cbdt': ColorGenerator(lambda *args: _not_impl('ufo', *args),
                           lambda *args: _not_impl('TTFont', *args)),
    'sbix': ColorGenerator(lambda *args: _not_impl('ufo', *args),
                           lambda *args: _not_impl('TTFont', *args)),
}

FLAGS = flags.FLAGS

# TODO move to config file?
flags.DEFINE_integer('upem', 1024, 'Units per em.')
flags.DEFINE_string('family', 'An Emoji Family', 'Family name.')
flags.DEFINE_enum('color_format', 'colr_0',
                  sorted(_COLOR_FORMAT_GENERATORS.keys()),
                  'Type of color font to generate.')
flags.DEFINE_string('output_file', '/tmp/AnEmojiFamily-Regular.ttf',
                    'Dest file, can be .ttf, .otf, or .ufo')

def _codepoints_from_filename(filename):
    match = regex.search(r'(?:[-_]?([0-9a-fA-F]{4,}))+', filename)
    if match:
        return tuple(int(s, 16) for s in match.captures(1))
    logging.warning(f'Bad filename {filename}; unable to extract codepoints')
    return None

def _nanosvg(filename):
    try:
        return SVG.parse(filename).tonanosvg()
    except Exception as e:
        logging.warning(f'{filename} failed: {e}')
    return None

def _inputs(filenames):
    for filename in filenames:
        codepoints = _codepoints_from_filename(filename)
        nanosvg = _nanosvg(filename)
        if codepoints and nanosvg:
            yield (filename, codepoints, nanosvg)

def _ufo(family, upem):
    ufo = ufoLib2.Font()
    ufo.info.familyName = family
    # set various font metadata; see the full list of fontinfo attributes at
    # http://unifiedfontobject.org/versions/ufo3/fontinfo.plist/
    ufo.info.unitsPerEm = upem

    # Must have .notdef and Win 10 Chrome likes a blank gid1 so make that space
    ufo.newGlyph('.notdef')
    space = ufo.newGlyph('.space')
    space.unicodes = [0x0020]
    space.width = upem
    ufo.glyphOrder = ['.notdef', '.space']

    return ufo

def _layer(ufo, idx):
    """UFO has a global set of layers.

    Each layer then has glyphs. For an N-layer COLR glyph we
    write the glyph into global layers 0..N-1. The UFO will end up with
    as many global layers as the "deepest" glyph.

    The only real significance is z-order so name on that basis.
    """
    name = f'z_{idx}'
    if name not in ufo.layers:
        ufo.newLayer(name)
    return ufo.layers[name]

def _make_ttfont(config, ufo, color_glyphs):
    if config.output_format == '.ufo':
        return None

    # Use skia-pathops to remove overlaps (i.e. simplify self-overlapping
    # paths) because the default ("booleanOperations") does not support
    # quadratic bezier curves (qcurve), which may appear
    # when we pass through nanosvg (e.g. arcs or stroked paths).
    ttfont = None
    if config.output_format == ".ttf":
        ttfont = ufo2ft.compileTTF(ufo, overlapsBackend="pathops")
    if config.output_format == ".otf":
        ttfont = ufo2ft.compileOTF(ufo, overlapsBackend="pathops")

    if not ttfont:
        raise ValueError(f'Unable to generate {color_format} {dest_format}')

    # Permit fixups where we can't express something adequately in UFO
    _COLOR_FORMAT_GENERATORS[config.color_format].apply_ttfont(ufo, color_glyphs, ttfont)

    return ttfont

def _write(ufo, ttfont, output_file):
    logging.info('Writing %s', output_file)

    if os.path.splitext(output_file)[1] == ".ufo":
        ufo.save(output_file, overwrite=True)
    else:
        ttfont.save(output_file)

def _not_impl(*_):
    raise NotImplementedError('%s not implemented' % FLAGS.color_format)

def _colr_ufo(colr_version, ufo, color_glyphs):
    # Sort colors so the index into colors == index into CPAL palette
    colors = sorted(set(chain.from_iterable((g.colors() for g in color_glyphs))))
    logging.debug('colors %s', colors)

    # KISS; use a single global palette
    ufo.lib[ufo2ft.constants.COLOR_PALETTES_KEY] = [[c.to_ufo_color() for c in colors]]

    # We created glyph_name on the default layer for the base glyph
    # Now create glyph_name on layers 0..N-1 for the colored layers
    for color_glyph in color_glyphs:
        # For COLRv0, paint is just the palette index
        # For COLRv1, it's a data structure describing paint
        layer_to_paint = []
        svg_units_to_font_units = color_glyph.transform_for_font_space()
        for idx, (paint, path) in enumerate(color_glyph.as_painted_layers()):
            glyph_layer = _layer(ufo, idx)

            if colr_version == 0:
                # COLRv0: draw using the first available color on the glyph_layer
                # Results for gradients will be suboptimal :)
                color = next(paint.colors())
                layer_to_paint.append((glyph_layer.name, colors.index(color)))

            elif colr_version == 1:
                # COLRv0: fill in gradient paint structures
                layer_to_paint.append((glyph_layer.name, paint.to_ufo_paint(colors)))

            else:
                raise ValueError(f'Unsupported COLR version: {colr_version}')

            # we've got a colored layer, put a glyph on it
            glyph = glyph_layer.newGlyph(color_glyph.glyph_name)
            glyph.width = ufo.info.unitsPerEm

            pen = TransformPen(glyph.getPen(), svg_units_to_font_units)
            skia_path(path).draw(pen)

        # each base glyph contains a list of (layer.name, paint info) in z-order
        base_glyph = ufo.get(color_glyph.glyph_name)
        base_glyph.lib[ufo2ft.constants.COLOR_LAYER_MAPPING_KEY] = layer_to_paint

def _svg_ttfont(ufo, color_glyphs, ttfont, zip=False):
    svg_table = ttLib.newTable('SVG ')
    svg_table.compressed = zip
    svg_table.docList = [(c.nsvg
                          # dumb sizing isn't useful
                          .remove_attributes(('width', 'height'))
                          # Firefox likes to render blank if present
                          .remove_attributes(('enable-background',))
                          # Required to match gid
                          .set_attributes((('id', f'glyph{c.glyph_id}'),))
                          .tostring(),
                          ttfont.getGlyphID(c.glyph_name),
                          ttfont.getGlyphID(c.glyph_name))
                         for c in color_glyphs]
    ttfont[svg_table.tableTag] = svg_table

def _generate_color_font(config, glyph_inputs):
    """Make a UFO and optionally a TTFont from svgs.

    Args:
        color_font_config: ColorFontConfig
        glyph_inputs: sequence of (filename, codepoints, nanosvg) tuples
    """
    ufo = _ufo(config.family, config.upem)
    base_gid = len(ufo.glyphOrder)
    color_glyphs = [ColorGlyph.create(ufo, filename, base_gid + idx, codepoints, nsvg)
                    for idx, (filename, codepoints, nsvg) in enumerate(glyph_inputs)]
    ufo.glyphOrder = ufo.glyphOrder + [g.glyph_name for g in color_glyphs]
    for g in color_glyphs:
        assert g.glyph_id == ufo.glyphOrder.index(g.glyph_name)

    _COLOR_FORMAT_GENERATORS[config.color_format].apply_ufo(ufo, color_glyphs)

    ttfont = _make_ttfont(config, ufo, color_glyphs)

    # TODO may wish to nuke 'post' glyph names

    return ufo, ttfont

def main(argv):
    config = ColorFontConfig(upem=FLAGS.upem,
                             family=FLAGS.family,
                             color_format=FLAGS.color_format,
                             output_format=os.path.splitext(FLAGS.output_file)[1])

    inputs = list(_inputs(argv[1:]))
    logging.info(f'{len(inputs)}/{len(argv[1:])} inputs prepared successfully')

    ufo, ttfont = _generate_color_font(config, inputs)

    _write(ufo, ttfont, FLAGS.output_file)
    logging.info('Wrote %s' % FLAGS.output_file)

if __name__ == "__main__":
    app.run(main)
<|endoftext|>


<commit_before>from django.test import TestCase
from uw_pws.util import fdao_pws_override
from uw_sws.util import fdao_sws_override
from course_grader.dao.person import PWS
from course_grader.dao.section import get_section_by_label
from course_grader.dao.term import get_term_by_year_and_quarter
from course_grader.views import *

@fdao_sws_override
@fdao_pws_override
class ViewFunctionsTest(TestCase):
    def test_clean_section_id(self):
        self.assertEquals(
            clean_section_id(
                '2013-spring-TRAIN-101-A-9136CCB8F66711D5BE060004AC494FFE'),
            '2013-spring-TRAIN-101-A-9136CCB8F66711D5BE060004AC494FFE')

        self.assertEquals(
            clean_section_id(
                '2013-spring-T RAIN-101-A-9136CCB8F66711D5BE060004AC494FFE'),
            '2013-spring-T_RAIN-101-A-9136CCB8F66711D5BE060004AC494FFE')

        self.assertEquals(
            clean_section_id(
                '2013-spring-AB&C-101-A-9136CCB8F66711D5BE060004AC494FFE'),
            '2013-spring-AB_C-101-A-9136CCB8F66711D5BE060004AC494FFE')

    def test_url_for_term(self):
        term = get_term_by_year_and_quarter(2013, 'spring')
        self.assertEquals(url_for_term(term), '/?term=2013-spring')

    def test_url_for_section(self):
        with self.settings(GRADEPAGE_HOST=''):
            self.assertEquals(
                url_for_section((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_section((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-T%20RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE'))

        with self.settings(GRADEPAGE_HOST='https://abc.edu'):
            self.assertEquals(
                url_for_section((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-TRAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_section((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-T%20RAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_grading_status(self):
        with self.settings(GRADEPAGE_HOST=''):
            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-TRAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-T%20RAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

        with self.settings(GRADEPAGE_HOST='https://abc.edu'):
            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-TRAIN-'
                    '101-A-9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-T%20RAIN-'
                    '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_graderoster(self):
        self.assertEquals(
            url_for_graderoster((
                '2013-spring-TRAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/graderoster/2013-spring-TRAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

        self.assertEquals(
            url_for_graderoster((
                '2013-spring-T RAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/graderoster/2013-spring-T%20RAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_import(self):
        self.assertEquals(
            url_for_import((
                '2013-spring-TRAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/import/2013-spring-TRAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

        self.assertEquals(
            url_for_import((
                '2013-spring-T RAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/import/2013-spring-T%20RAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_export(self):
        self.assertEquals(
            url_for_export((
                '2013-spring-TRAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/export/2013-spring-TRAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

        self.assertEquals(
            url_for_export((
                '2013-spring-T RAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/export/2013-spring-T%20RAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_section_status_params(self):
        section = get_section_by_label('2013,summer,TRAIN,101/A')
        user = PWS().get_person_by_netid('javerage')

        p = section_status_params(section, user)

        self.assertEquals(p['grading_period_open'], False)
        self.assertEquals(
            p['grade_submission_deadline'], '2013-08-27T17:00:00')
        self.assertEquals(p['grading_status'], None)
<commit_msg>fix summer status test<commit_after>from django.test import TestCase
from uw_pws.util import fdao_pws_override
from uw_sws.util import fdao_sws_override
from course_grader.dao.person import PWS
from course_grader.dao.section import get_section_by_label
from course_grader.dao.term import get_term_by_year_and_quarter
from course_grader.views import *

@fdao_sws_override
@fdao_pws_override
class ViewFunctionsTest(TestCase):
    def test_clean_section_id(self):
        self.assertEquals(
            clean_section_id(
                '2013-spring-TRAIN-101-A-9136CCB8F66711D5BE060004AC494FFE'),
            '2013-spring-TRAIN-101-A-9136CCB8F66711D5BE060004AC494FFE')

        self.assertEquals(
            clean_section_id(
                '2013-spring-T RAIN-101-A-9136CCB8F66711D5BE060004AC494FFE'),
            '2013-spring-T_RAIN-101-A-9136CCB8F66711D5BE060004AC494FFE')

        self.assertEquals(
            clean_section_id(
                '2013-spring-AB&C-101-A-9136CCB8F66711D5BE060004AC494FFE'),
            '2013-spring-AB_C-101-A-9136CCB8F66711D5BE060004AC494FFE')

    def test_url_for_term(self):
        term = get_term_by_year_and_quarter(2013, 'spring')
        self.assertEquals(url_for_term(term), '/?term=2013-spring')

    def test_url_for_section(self):
        with self.settings(GRADEPAGE_HOST=''):
            self.assertEquals(
                url_for_section((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_section((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-T%20RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE'))

        with self.settings(GRADEPAGE_HOST='https://abc.edu'):
            self.assertEquals(
                url_for_section((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-TRAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_section((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/section/2013-spring-T%20RAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_grading_status(self):
        with self.settings(GRADEPAGE_HOST=''):
            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-TRAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-T%20RAIN-101-'
                    'A-9136CCB8F66711D5BE060004AC494FFE'))

        with self.settings(GRADEPAGE_HOST='https://abc.edu'):
            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-TRAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-TRAIN-'
                    '101-A-9136CCB8F66711D5BE060004AC494FFE'))

            self.assertEquals(
                url_for_grading_status((
                    '2013-spring-T RAIN-101-A-'
                    '9136CCB8F66711D5BE060004AC494FFE')), (
                    '/api/v1/grading_status/2013-spring-T%20RAIN-'
                    '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_graderoster(self):
        self.assertEquals(
            url_for_graderoster((
                '2013-spring-TRAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/graderoster/2013-spring-TRAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

        self.assertEquals(
            url_for_graderoster((
                '2013-spring-T RAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/graderoster/2013-spring-T%20RAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_import(self):
        self.assertEquals(
            url_for_import((
                '2013-spring-TRAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/import/2013-spring-TRAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

        self.assertEquals(
            url_for_import((
                '2013-spring-T RAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/import/2013-spring-T%20RAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_url_for_export(self):
        self.assertEquals(
            url_for_export((
                '2013-spring-TRAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/export/2013-spring-TRAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

        self.assertEquals(
            url_for_export((
                '2013-spring-T RAIN-101-A-'
                '9136CCB8F66711D5BE060004AC494FFE')), (
                '/api/v1/export/2013-spring-T%20RAIN-'
                '101-A-9136CCB8F66711D5BE060004AC494FFE'))

    def test_section_status_params(self):
        section = get_section_by_label('2013,summer,TRAIN,101/A')
        user = PWS().get_person_by_netid('javerage')

        p = section_status_params(section, user)

        self.assertEquals(p['grading_period_open'], False)
        self.assertEquals(
            p['grade_submission_deadline'], '2013-08-27T17:00:00')
        self.assertEquals(
            p['grading_status'], (
                'Summer B-term grade submission opens on August 16 '
                'at  8:00 AM PDT.'))
<|endoftext|>


<commit_before>__version__ = '0.0.91'
<commit_msg>fixed a merge error<commit_after>__version__ = '0.0.92'
<|endoftext|>