#-*- coding: utf-8 -*-

from pathlib import Path
import sqlite3, os, copy, glob, time, threading, datetime, shutil, logging, json, csv
from itertools import chain
from PIL import Image, ImageSequence
import my_utils as mutl

LIST_DELIMITER = ';'
sqlite3.dbapi2.converters['DATETIME'] = sqlite3.dbapi2.converters['TIMESTAMP']

class DataBaseManager():
    """
    GUIメインからのデータベース操作をSQLを意識せずに済むようにラッピングする。
    """
    logger = logging.getLogger('MyDBApp')

    DATABASE_NAME = 'database.sqlite'

    FILE_ENCODING = 'utf-8'

    DATA_DIR = 'data'
    TAG_IMAGE_DIR = 'tag_image'
    BACKUP_DIR = 'backup'
    FILE_NUMBER_OF_DIGITS = 5

    SUPPORTED_EXT = ["jpg", "jpeg", "png", "bmp", "gif", "zip"]

    TEMPLATE_MAIN_COLUMN = {
        'Title': 'text primary key',
        'InitialCharacter': 'text',
        'Updated': 'datetime',
        'FileNum': 'Integer',
        'Size': 'Real',
        'Link':'text',
        'IsFavorite': 'Integer',
        'favorite': 'text',
        'chapter': 'text'
    }

    TEMPLATE_TAGS_COLUMN = {
        'Name': 'text primary key',
        'InitialCharacter': 'text',
        'Link': 'text',
        'Image': 'text',
        'IsFavorite': 'Integer'
    }

    db_root = ''
    data_dir = ''
    tag_dir = ''

    record_num = 0

    # ファイル複製スレッド
    file_op_progress = {
        'task_num':0,
        'task_index':0,
        'title':'',
        'file_num':0,
        'done_file':0,
        'all_size':0,
        'done_size':0,
        'speed':0,
        'remaining_time':0.0
        }
    is_cancel = False

    # cursorのスレッド以外で発行されたSQL文を溜め込む
    sql_tasks = []

    dbm_thread_id = 0

    file_op_tasks = []
    file_op_thread = threading.Thread() # ファイル操作用のスレッド

    def __init__(self):
        self.logger.debug('get instance')

    def __del__(self):
        self.logger.debug('delete instance')
        self.close()

    # データベース操作
    def close(self):

        if self.file_op_thread.is_alive():
            self.file_op_thread.join()

        self.sql_tasks = []

        if self.db_root != '':
            self.cursor.close()
            self.connection.close()
            self.db_root == ''

    def database_is_exist(self, db_root):
        db_path = os.path.join(db_root, self.DATABASE_NAME)
        return os.path.exists(db_path)

    def connect_database(self, db_root):

        db_path = os.path.join(db_root, self.DATABASE_NAME)
        if not os.path.exists(db_path):
            return False

        self.db_root = db_root
        self.data_dir = os.path.join(self.db_root, self.DATA_DIR)
        self.tag_dir = os.path.join(self.db_root, self.TAG_IMAGE_DIR)

        self.logger.debug('open database')

        self.connection = sqlite3.connect(db_path, detect_types = sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
        self.cursor = self.connection.cursor()

        return self.get_free_space() / 1024 #MiBで帰ってくる

    def create_database(self, db_root, db_option={}):
        """
        概要:
            新規データベースを作成する。
        引数:
            db_root: データベースのルートディレクトリ
            additional_tags: ユーザが任意で追加していくタグのdict
                - key:タグ名、value:tuple(sqlite内でのデータ型, 最大登録数)
        返り値:
            db_rootにdatabase.sqliteが存在する場合Falseリターン
        メモ:
        """

        # すでにデータベースがある場合はFalseリターン
        db_path = os.path.join(db_root, self.DATABASE_NAME)
        if os.path.exists(db_path):
            return False

        # データベースとデータ置き場の作成
        self.db_root = db_root
        self.connection = sqlite3.connect(db_path, detect_types = sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
        self.cursor = self.connection.cursor()

        self.data_dir = os.path.join(self.db_root, self.DATA_DIR)
        if not os.path.exists(self.data_dir):
            os.mkdir(self.data_dir)

        self.tag_dir = os.path.join(self.db_root, self.TAG_IMAGE_DIR)
        if not os.path.exists(self.tag_dir):
            os.mkdir(self.tag_dir)

        # Mainテーブルを作成する。
        table_name = 'MainTable'
        table_columns = copy.deepcopy(self.TEMPLATE_MAIN_COLUMN)
        table_columns.update(db_option)
        tmp_str = ','.join(['{} {}'.format(key, value) for key,value in table_columns.items()])
        sql = 'create table if not exists {} ({})'.format(table_name, tmp_str)

        self.logger.debug(sql)
        self.cursor.execute(sql)

        # 追加タグ記録用のテーブルを作成する
        for key in db_option.keys():
            table_name = key
            tmp_str = ','.join(['{} {}'.format(key, value) for key,value in self.TEMPLATE_TAGS_COLUMN.items()])
            sql = 'create table if not exists {} ({})'.format(table_name, tmp_str)
            self.logger.debug(sql)
            self.cursor.execute(sql)

        self.connection.commit()

        return True

    def copy_database(self, dst_path):

        if self.file_op_thread.is_alive():
            return False

        self.file_op_tasks = []
        data_list = self.get_items(['Title', 'FileNum', 'Size'])

        dst_data_path = os.path.join(dst_path, self.DATA_DIR)

        all_size = 0
        for row in data_list:

            title = row[0]
            filenum = row[1]
            src_size = row[2]
            all_size += src_size

            src_path = os.path.join(self.data_dir, title)
            new_path = os.path.join(dst_data_path, title)

            # ファイルコピーのタスクを作成する -簡易版
            self.file_op_tasks.append({
                'title':title,
                'src_path':src_path,
                'src_size':src_size,
                'dst_path':new_path,
                'file_num':filenum
                })

        os.mkdir(dst_data_path)

        dst_tag_path = os.path.join(dst_path, self.TAG_IMAGE_DIR)
        shutil.copytree(self.tag_dir, dst_tag_path)

        src_db_path = os.path.join(self.db_root, self.DATABASE_NAME)
        dst_db_path = os.path.join(dst_path, self.DATABASE_NAME)
        shutil.copyfile(src_db_path, dst_db_path)

        self.file_op_thread = threading.Thread(target=self._copy_database,daemon=False)
        self.file_op_thread.start()

        return True

    def _copy_database(self):

        # TODO: このやりかただと進捗表示が細かくできない

        self.file_op_progress['task_num'] = len(self.file_op_tasks)

        self.file_op_progress['done_size'] = 0
        self.file_op_progress['all_size'] = 0
        for ftask in self.file_op_tasks:
            self.file_op_progress['all_size'] += ftask['src_size']

        start = time.time()

        for t_idx, ftask in enumerate(self.file_op_tasks):

            # 初期設定
            title = ftask['title']
            trg_path = ftask['dst_path']
            src_path = ftask['src_path']

            self.file_op_progress['task_index'] = t_idx+1
            self.file_op_progress['title'] = title
            self.file_op_progress['file_num'] = ftask['file_num']

            shutil.copytree(src_path, trg_path)

            # 処理速度、残り時間予測
            self.file_op_progress['done_size'] += ftask['src_size']
            process_time = time.time() - start
            if process_time != 0:
                self.file_op_progress['speed'] = self.file_op_progress['done_size']/process_time # [MiB/s]

            if self.file_op_progress['speed'] != 0:
                self.file_op_progress['remaining_time'] = (self.file_op_progress['all_size'] - self.file_op_progress['done_size']) / self.file_op_progress['speed']

    def get_free_space(self):
        """
        接続中のデータベースのディスク空き容量を取得する
        """
        try:
            disk_info = shutil.disk_usage(self.db_root)
            return disk_info.free / (1024*1024)
        except:
            return 0

    # MainTable操作
    def get_db_info(self):
        size_list = self.get_items('Size', convert=True)
        num = len(size_list)
        if num == 0:
            sum_size = 0
        else:
            sum_size = sum(size_list)
        return num, sum_size

    def insert_records(self, records, move_file=False):
        """
        概要:
            レコードを登録する。
            登録のみでファイルの移動はせず、クラス内のfile_op_tasksに追加される。
            別途start_file_operationでコピータスクを実行する。
            本関数を小分けで使用してタスクを貯めてからまとめて実行もできるはず...
        引数:
            records: 下記の構造を持つ辞書のリスト
                {
                    path:登録ファイル群のフォルダのパス（必須）
                    Title:登録タイトル（レコードの主キー、必須）
                    values_dict:{その他のカラム内容の辞書、任意}
                }
            move_file:コピーではなくファイルを移動する。
        返り値:
            err_info: 'title: エラー内容'のstringのリスト
        メモ:
            ・err_infoは辞書のほうが使い勝手がよいかも
            ・未実装のエラーハンドリング
                - values_dictの不正なカラム
                - pathとtitleがない場合
            ・上書きができるようにすると色々と幅が広がる
                - 上書きモードと追加モードなどなど
        """

        op_mode = 'move' if move_file else 'copy'
        title_list = self.get_items('Title', convert=True)

        self.file_op_tasks = []

        err_info = []
        for info in records:

            # 情報の取り出し
            title = info['Title']
            src_path = info['path']
            values_dict = copy.deepcopy(info['values_dict'])

            # 同名タイトル
            if title in title_list:
                err_info.append('{} : 同名タイトルがあります。'.format(title))
                break

            # パスの正当性確認
            if not os.path.exists(src_path):
                err_info.append('{} : 該当するフォルダがありません。'.format(title))
                break

            # ファイル一覧取得
            file_list, file_size = mutl.search_files_deep(src_path, self.SUPPORTED_EXT)
            file_size /= (1024 * 1024)
            #file_list = sorted(list(chain.from_iterable([glob.glob(os.path.join(src_path, "*." + ext)) for ext in self.SUPPORTED_EXT])))

            # ファイルがない場合
            if len(file_list) == 0:
                err_info.append('{} : 該当するファイルがありません。'.format(title))
                break

            # レコードを作成する
            try:
                values_dict['Title'] = title
                values_dict['FileNum'] = 0
                values_dict['Size'] = 0.0
                values_dict['Link'] = ''
                values_dict['IsFavorite'] = 0
                values_dict['favorite'] = []
                values_dict['chapter'] = []
                sql_tmp, sql_values = self._convert_dict4sql(values_dict)
                sql = 'insert into MainTable {}'.format(sql_tmp)
                self.logger.debug('{} {}'.format(sql, sql_values))
                self.cursor.execute(sql, sql_values)
            except:
                err_info.append((title, 'SQL Insert Error'))
                break

            # ファイルコピーのタスクを作成する
            self.file_op_tasks.append({
                'operation':op_mode,
                'title':title,
                'src_list':file_list,
                'src_size':file_size,
                'dst_path':os.path.join(self.data_dir, title),
                'init_num':0,
                'init_size':0.0
                })

        self.connection.commit()

        return err_info

    def add_files(self, title, file_list):

        if len(file_list) == 0:
            return False

        self.file_op_tasks = []

        file_size = 0
        for fl in file_list:
            file_size += os.path.getsize(fl)
        file_size /= (1024 * 1024)

        init_info = self.get_items(['FileNum','Size'], title=title)

        # ファイルコピーのタスクを作成する
        self.file_op_tasks.append({
            'operation':'copy',
            'title':title,
            'src_list':sorted(file_list, key=mutl.numericalSort),
            'src_size':file_size,
            'dst_path':os.path.join(self.data_dir, title),
            'init_num':init_info[0][0],
            'init_size':init_info[0][1]
            })

        return True

    def delete_record(self, titles):
        """
        概要:
            指定されたレコードと該当するファイルを削除する。
        引数:
            titles: 削除するレコードのtitleのリスト
        返り値:
            False：前のスレッドが生きている場合にFalseを返す。
        メモ:
            ファイル削除は別スレッドだが、shutil.rmtreeでの一括消去のため進捗は確認できない。
            copyのタスクと同じ形式でまとめたいところ。
        """

        if self.file_op_thread.is_alive():
            return False

        for title in titles:
            sql = 'DELETE FROM MainTable WHERE Title="{}"'.format(title)
            self.logger.debug(sql)
            self.cursor.execute(sql)

        self.file_op_thread = threading.Thread(target=self._delete_titles, args=([titles]),daemon=False)
        self.file_op_thread.start()

        self.connection.commit()

        return True

    def delete_files(self, title,file_idx):
        """
        概要:
        引数:
        返り値:
        メモ:
        """
        if len(file_idx) == 0:
            return False

        self.file_op_tasks = []

        init_info = self.get_items(['FileNum','Size','favorite','chapter'], title=title, convert=True)

        init_favorite = []
        for i in init_info['favorite'][0]:
            if i.isdecimal():
                init_favorite.append(int(i))
        init_chapter = []
        for i in init_info['chapter'][0]:
            if i.isdecimal():
                init_chapter.append(int(i))

        # ファイルコピーのタスクを作成する
        self.file_op_tasks.append({
            'operation':'delete',
            'title':title,
            'src_list':sorted(file_idx),
            'src_size':0, # TODO: 現状、使っていない
            'dst_path':os.path.join(self.data_dir, title),
            'init_num':init_info['FileNum'][0][0],
            'init_size':init_info['Size'][0][0],
            'init_favorite': copy.deepcopy(init_favorite),
            'init_chapter':copy.deepcopy(init_chapter)
            })

        return True

    def start_file_operation(self):
        """
        概要:
            insert_recordsで発生したfile_op_tasksを別スレッドで実行する。
            レコード内容を更新するsqlをsql_tasksとして溜め込むため、コピー完了後にresolve_sql_tasksの実行が必要。
            スレッドの生存は、file_op_is_aliveで確認できる。
            コピーの進捗状況は、get_file_op_progressで取得できる。
        引数:
        返り値:
            False：前のスレッドが生きている場合にFalseを返す。
        メモ:
            ・sqlが、cursorを取得したスレッドでしか実行できないので、タスクとしてためている。
                - sqlの実施だけメインスレッドに帰ってきて実行とかできないだろうか...
            ・キャンセル操作への対応
        """

        if self.file_op_thread.is_alive():
            return False

        self.file_op_thread = threading.Thread(target=self._run_file_operation,daemon=False)
        self.file_op_thread.start()

        return True

    def sort_files(self, title,ref_index, insert_index):

        if len(insert_index) == 0:
            return

        title_dir, file_list, _ = self.get_file_list(title)
        init_info = self.get_items(['favorite','chapter'], title=title, convert=True)

        init_favorite = set([])
        for i in init_info['favorite'][0]:
            if i.isdecimal():
                init_favorite.add(int(i))
        init_chapter = set([])
        for i in init_info['chapter'][0]:
            if i.isdecimal():
                init_chapter.add(int(i))

        # 対象範囲のインデックスをすべて取得
        tmp_set = set(insert_index) | set([ref_index])
        trg_idx = set(range(min(tmp_set),max(tmp_set)+1))
        # 入れ替え対象のインデックスを除く, リスト化して指定位置に挿入
        new_order = sorted(trg_idx ^ insert_index)
        ref_pos = new_order.index(ref_index)
        new_order[ref_pos:ref_pos] = sorted(insert_index)

        # ソート１ - 元ファイルをいったん別名にして避難しつつ、ファイル名変更タスクの作成
        old_order = sorted(trg_idx)
        sort_task = []
        new_favorite = set([])
        new_chapter = set([])
        for i, old_idx in enumerate(new_order):

            new_idx = old_order[i]
            if old_idx == new_idx:
                continue

            if old_idx in init_favorite:
                init_favorite.remove(old_idx)
                new_favorite.add(new_idx)
            if old_idx in init_chapter:
                init_chapter.remove(old_idx)
                new_chapter.add(new_idx)

            fname = file_list[old_idx]
            old_idx_str, ext = os.path.splitext(fname) # インデックスと拡張子に分解

            old_path = os.path.join(title_dir, fname)
            tmp_path = os.path.join(title_dir, '_' + fname)
            os.rename(old_path, tmp_path)
            #print('{} -> {}'.format(old_path, tmp_path))

            new_idx_str = str(new_idx).zfill(self.FILE_NUMBER_OF_DIGITS)
            new_path = os.path.join(title_dir, new_idx_str + ext)

            sort_task.append((tmp_path, new_path))

            if ext == '.zip':
                old_thum_path = os.path.join(title_dir, '__thumbnail__', old_idx_str + '.png')
                tmp_thum_path = os.path.join(title_dir, '__thumbnail__', '_' + old_idx_str + '.png')
                os.rename(old_thum_path, tmp_thum_path)
                #print('{} -> {}'.format(old_thum_path, tmp_thum_path))

                new_thum_path = os.path.join(title_dir, '__thumbnail__', new_idx_str + '.png')
                sort_task.append((tmp_thum_path, new_thum_path))

        # ソート２ - ファイル名変更の実施
        for stask in sort_task:
            os.rename(stask[0], stask[1])
            #print('{} -> {}'.format(stask[0], stask[1]))

        new_favorite |= init_favorite
        new_chapter |= init_chapter

        # データベースの情報変更
        values_dict = {}
        values_dict['favorite'] = sorted(new_favorite)
        values_dict['chapter'] = sorted(new_chapter)
        #print(values_dict)

        self.update_record(title, values_dict=values_dict)

    def update_record(self, title, values_dict):
        """
        登録済みのデータのアップデート
        """
        data_list = []

        for key, value in values_dict.items():

            if key is 'Title':
                b_path = os.path.join(self.data_dir, title)
                n_path = os.path.join(self.data_dir, value)
                os.rename(b_path, n_path)

            if type(value) is list:
                tmp_val = LIST_DELIMITER.join([str(i) for i in value])
            else:
                tmp_val = value
            data_list.append('{0}="{1}"'.format(key,tmp_val))

        sql_set_data = ','.join(data_list)
        sql = 'UPDATE MainTable SET {0} WHERE Title="{1}"'.format(sql_set_data, title)

        # cursorのスレッドのみ実行可能なため、それ以外はタスクとして溜め込む
        try:
            self.logger.debug(sql)
            self.cursor.execute(sql)
            self.connection.commit()
        except:
            self.logger.debug('before SQL STACKED')
            self.sql_tasks.append(sql)

    def title_is_exist(self, title):
        sql = 'SELECT COUNT(*) FROM MainTable WHERE Title="{}"'.format(title)
        self.cursor.execute(sql)
        result = self.cursor.fetchall()
        return result[0][0] != 0

    def resolve_sql_tasks(self):
        """
        未実行のSQL文を実行する
        """
        for sql in self.sql_tasks:
            self.logger.debug(sql)
            self.cursor.execute(sql)

        self.sql_tasks = []
        self.connection.commit()

    def _run_file_operation(self):

        # TODO: 中断操作対応

        self.file_op_progress['task_num'] = len(self.file_op_tasks)

        self.file_op_progress['done_size'] = 0
        self.file_op_progress['all_size'] = 0
        for ftask in self.file_op_tasks:
            self.file_op_progress['all_size'] += ftask['src_size']

        start = time.time()

        for t_idx, ftask in enumerate(self.file_op_tasks):

            # 初期設定
            op_mode = ftask['operation']
            title = ftask['title']
            trg_path = ftask['dst_path']
            src_list = ftask['src_list']
            idx_offset = ftask['init_num']

            values_dict = {}
            values_dict['FileNum'] = ftask['init_num']
            values_dict['Size'] = ftask['init_size']

            self.file_op_progress['task_index'] = t_idx+1
            self.file_op_progress['title'] = title
            self.file_op_progress['file_num'] = len(src_list)
            self.file_op_progress['done_file'] = 0

            if op_mode in ['move', 'copy']:

                if not os.path.exists(trg_path):
                    os.mkdir(trg_path)

                for i, file_path in enumerate(src_list):
                    _, ext = os.path.splitext(file_path)
                    new_name = str(i + idx_offset).zfill(self.FILE_NUMBER_OF_DIGITS)
                    new_path = os.path.join(trg_path, new_name)
                    if ext == '.gif':
                        thum_dir = os.path.join(trg_path, '__thumbnail__')
                        if not os.path.exists(thum_dir):
                            os.mkdir(thum_dir)
                        self._gif_to_zip(file_path, new_path, thum_dir)
                        fsize = os.path.getsize(new_path + '.zip') / (1024*1024)
                        values_dict['Size'] += fsize
                    else:
                        # TODO: 上書き操作は仮で禁止にしている
                        new_path = new_path + ext
                        if not os.path.exists(new_path):
                            if op_mode == 'move':
                                shutil.move(file_path, new_path)
                            elif op_mode == 'copy':
                                shutil.copyfile(file_path, new_path)
                        fsize = os.path.getsize(new_path) / (1024*1024)
                        values_dict['Size'] += fsize
                    values_dict['FileNum'] += 1
                    self.file_op_progress['done_file'] += 1

                    # 処理速度、残り時間予測
                    self.file_op_progress['done_size'] += fsize
                    process_time = time.time() - start
                    if process_time != 0:
                        self.file_op_progress['speed'] = self.file_op_progress['done_size']/process_time # [MiB/s]

                    if self.file_op_progress['speed'] != 0:
                        self.file_op_progress['remaining_time'] = (self.file_op_progress['all_size'] - self.file_op_progress['done_size']) / self.file_op_progress['speed']

                values_dict['Updated'] = datetime.datetime.now()

            elif op_mode == 'delete':

                # deleteの場合はindexのリストが渡されている想定
                # renameも兼ねるため、結局trg_path内を全探索する

                title_dir, file_list, _ = self.get_file_list(title)

                new_favorite = []
                new_chapter = []
                count_new_idx = 0
                for i, fname in enumerate(file_list):

                    idx_str, ext = os.path.splitext(fname) # インデックスと拡張子に分解
                    idx = int(idx_str)

                    fpath = os.path.join(title_dir, fname)

                    if ext == '.zip':
                        thum_name = idx_str + '.png'
                        thum_path = os.path.join(title_dir, '__thumbnail__', thum_name)

                    if idx in src_list:
                        fsize = os.path.getsize(fpath) / (1024*1024)
                        os.remove(fpath)
                        if ext == '.zip':
                            os.remove(thum_path)
                        values_dict['FileNum'] -= 1
                        values_dict['Size'] -= fsize
                        self.file_op_progress['done_file'] += 1
                    else:
                        if idx != count_new_idx:
                            new_idx_str = str(count_new_idx).zfill(self.FILE_NUMBER_OF_DIGITS)
                            new_path = os.path.join(title_dir, new_idx_str + ext)
                            os.rename(fpath, new_path)
                            if ext == '.zip':
                                new_thum_path = os.path.join(title_dir, '__thumbnail__', new_idx_str + '.png')
                                os.rename(thum_path, new_thum_path)
                        if idx in ftask['init_favorite']:
                            new_favorite.append(count_new_idx)
                        if idx in ftask['init_chapter']:
                            new_chapter.append(count_new_idx)
                        count_new_idx += 1

                values_dict['favorite'] = sorted(new_favorite)
                values_dict['chapter'] = sorted(new_chapter)

            # データベース情報更新
            self.update_record(title, values_dict=values_dict)
            self.logger.debug('Copy Operation Finished')

    def _delete_titles(self, titles):

        for title in titles:
            del_path = os.path.join(self.data_dir, title)
            if os.path.exists(del_path):
                shutil.rmtree(del_path)

        self.logger.debug('Delete Operation Finished')

    def file_op_is_alive(self):
        return self.file_op_thread.is_alive()

    def get_file_op_progress(self):
        return copy.deepcopy(self.file_op_progress)

    def get_items(self, col_name, title=None, convert=False):
        """
        col_namesのリストに対するSELECT結果（tuple）を返す。
        covertをTrueにすると、tupleから変換して返す。
        　・col_nameがstrならlist
        　・col_nameがlistならcol_nameをkeyとしたdict
        """

        if type(col_name) is list:
            sql = 'SELECT {} FROM MainTable'.format(','.join(col_name))
        elif type(col_name) is str:
            sql = 'SELECT {} FROM MainTable'.format(col_name)
        else:
            return []

        if not title is None:
            sql += ' WHERE Title="{}"'.format(title)

        self.logger.debug(sql)
        self.cursor.execute(sql)

        result = self.cursor.fetchall()

        if convert:
            if type(col_name) is list:
                tmp_dict = {}
                for cl in col_name:
                    tmp_dict[cl] = []
                for row in result:
                    for i, cl in enumerate(col_name):
                        tmp_dict[cl].append(self._convert_list(row[i]))
                return tmp_dict
            elif type(col_name) is str:
                tmp_list = []
                for row in result:
                    tmp_list += self._convert_list(row[0])
                return tmp_list
        else:
            return result

    def get_titles(self, filter_option={}, init_chars=[], enable_or=False, sort_option=('Updated',True)):

        col_names = ['Title', 'FileNum', 'IsFavorite', 'InitialCharacter'] + list(filter_option.keys())

        sort_order = 'DESC' if sort_option[1] else 'ASC'
        sql = 'SELECT {} FROM MainTable ORDER BY {} {}'.format(','.join(col_names), sort_option[0], sort_order)
        ret = self.cursor.execute(sql)

        title_list = []
        for row in ret.fetchall():

            if len(init_chars) != 0:
                if not row[3] in init_chars:
                    continue

            if len(filter_option.keys()) == 0:
                flag = True
            else:
                flag = False if enable_or else True
                for i in range(4, len(col_names)):
                    # 値取得
                    ref_val = self._convert_list(row[i])
                    key = col_names[i]
                    f_val = filter_option[key]
                    for fv in f_val:
                        if enable_or:
                            flag |= (fv in ref_val)
                        else:
                            flag &= (fv in ref_val)
            if flag:
                title_list.append((row[0],row[1],row[2]))

        return title_list

    def get_file_list(self, title):

        file_dir = os.path.join(self.data_dir, title)

        if not os.path.exists(file_dir):
            return '', []

        #登録時に拡張子はフィルタされるのでファイルすべてを取得しているが、早くなるかは不明
        #tmp_list = sorted(glob.glob(os.path.join(file_dir, "*")))
        #file_list = [os.path.basename(r) for r in tmp_list]

        tmp_list, file_size = mutl.search_files(file_dir, self.SUPPORTED_EXT)
        #tmp_list = sorted(list(chain.from_iterable([glob.glob(os.path.join(file_dir, "*." + ext)) for ext in self.SUPPORTED_EXT])))

        file_list = [os.path.basename(r) for r in tmp_list]

        return file_dir, file_list, file_size

    # 追加項目操作
    def get_additional_table(self):
        """
        ユーザ作成のタグ一覧の取得
        (= MainTable以外のテーブル名の一覧)
        """
        table_list = []
        self.cursor.execute("select * from sqlite_master where type='table'")
        for x in self.cursor.fetchall():
            if not x[1] == 'MainTable':
                table_list.append(x[1])
        return table_list

    def add_tag(self, table, values_dict):
        """
        新規タグを追加する
            tagはMainTable内に存在するタグ種別名
            valuesは追加するタグ内容のリスト[Name, InitialCharacter]
        Nameが重複した場合はFalseリターン
        """
        if not 'Name' in values_dict.keys():
            return False
        elif self.tag_is_exist(table, values_dict['Name']):
            return False

        try:
            sql_tmp, sql_values = self._convert_dict4sql(values_dict)
            sql = 'insert into {} {}'.format(table, sql_tmp)
            #sql = 'insert into {}(Name, InitialCharacter, Link, IsFavorite, Image) VALUES(?,?,?,?,?)'.format(table)
            self.logger.debug('{} {}'.format(sql, sql_values))
            self.cursor.execute(sql, sql_values)
            self.connection.commit()

            return True
        except:
            return False

    def update_tag(self, table, tag_name, values_dict):
        """
        タグの情報を変更する。
        """
        if 'Name' in values_dict.keys():
            if self.tag_is_exist(table, values_dict['Name']):
                return False

        data_list = []

        for key, value in values_dict.items():

            if type(value) is list:
                tmp_val = LIST_DELIMITER.join([str(i) for i in value])
            else:
                tmp_val = value

            if key == 'Image':
                # valueが空欄の場合は削除指令

                im_path = self.get_tag_image(table,tag_name)
                try:
                    os.remove(im_path)
                except:
                    self.logger.debug('Failed to remove file. {}'.format(im_path))
                finally:
                    if value == '':
                        tmp_val = ''
                    else:
                        tmp_val = self._asign_tag_image(table, value, tag_name)

            data_list.append('{0}="{1}"'.format(key,tmp_val))

        sql_set_data = ','.join(data_list)
        sql = 'UPDATE {0} SET {1} WHERE Name="{2}"'.format(table, sql_set_data, tag_name)

        try:
            self.logger.debug(sql)
            self.cursor.execute(sql)
            self.connection.commit()
        except:
            return False

        # タグ名を変更した場合 - MainTableにも反映する
        if 'Name' in values_dict.keys():
            old_name = tag_name
            new_name = values_dict['Name']
            update_list = [] # tuple(Title, NewList)

            self._update_tag_image(table, old_name, new_name)

            sql = 'SELECT Title,{} FROM MainTable'.format(table)
            ret = self.cursor.execute(sql)

            for row in ret.fetchall():
                title = row[0]
                tag_list = self._convert_list(row[1])
                if old_name in tag_list:
                    new_list = [new_name if t == old_name else t for t in tag_list]
                    update_list.append((title,{table:copy.deepcopy(new_list)}))

            for ul in update_list:
                self.update_record(title=ul[0],values_dict=ul[1])

        return True

    def delete_tags(self, table, names):

        for name in names:
            sql = 'DELETE FROM {} WHERE Name="{}"'.format(table, name)

            im_path = self.get_tag_image(table, name)
            if im_path != '':
                try:
                    os.remove(im_path)
                except:
                    self.logger.debug('Failed to remove file. {}'.format(im_path))

            self.logger.debug(sql)
            self.cursor.execute(sql)
            self.connection.commit()

            # MainTableも変更しに行く
            update_list = [] # tuple(Title, NewList)
            sql = 'SELECT Title,{} FROM MainTable'.format(table)
            ret = self.cursor.execute(sql)

            for row in ret.fetchall():
                title = row[0]
                tmp_list = self._convert_list(row[1])

                if name in tmp_list:
                    tmp_list.remove(name)
                    update_list.append((title,{table:copy.deepcopy(tmp_list)}))
            for ul in update_list:
                self.update_record(title=ul[0],values_dict=ul[1])

        return

    def tag_is_exist(self, table, name):
        sql = 'SELECT COUNT(*) FROM {} WHERE Name="{}"'.format(table, name)
        self.cursor.execute(sql)
        result = self.cursor.fetchall()
        return result[0][0] != 0

    def get_tag_image(self, table, name):
        sql = 'SELECT Image FROM {} WHERE Name="{}"'.format(table, name)
        self.cursor.execute(sql)
        result = self.cursor.fetchall()
        if result[0][0] == '':
            return ''
        else:
            im_name = '{}{}'.format(name, result[0][0])
            return os.path.join(self.tag_dir, table, im_name)

    def _asign_tag_image(self, table, src_path, tag_name):

        tmp_path = os.path.join(self.tag_dir, table)
        if not os.path.exists(tmp_path):
            os.mkdir(tmp_path)

        _, ext = os.path.splitext(src_path)
        new_name = '{}{}'.format(tag_name, ext)

        try:
            shutil.copyfile(src_path, os.path.join(tmp_path, new_name))
        except:
            self.logger.debug('Failed to copy file. {}'.format(src_path))

        return ext

    def _update_tag_image(self, table, old_name, new_name):

        sql = 'SELECT Image FROM {} WHERE Name="{}"'.format(table, new_name)
        self.cursor.execute(sql)
        result = self.cursor.fetchall()
        if result[0][0] == '':
            return

        ext = result[0][0]

        old_im_path = os.path.join(self.tag_dir, table, '{}{}'.format(old_name, ext))
        new_im_path = os.path.join(self.tag_dir, table, '{}{}'.format(new_name, ext))

        try:
            os.rename(old_im_path, new_im_path)
        except:
            self.logger.debug('Failed to change name. {} -> {}'.format(old_im_path, new_im_path))

    def get_tag_list(self, tag):
        sql = 'SELECT * FROM {}'.format(tag)
        self.cursor.execute(sql)
        return self.cursor.fetchall()

    def get_tag_items_with_num(self, tag_table, init_chars=[]):
        """
        タグ内容に該当する項目数をカウントして返す。
        リスト項目があるためfor文ループをしている
        """
        sql = 'SELECT Name,InitialCharacter,IsFavorite FROM {}'.format(tag_table)

        tag_info = {}
        self.cursor.execute(sql)
        for row in self.cursor.fetchall():
            if len(init_chars) != 0:
                if row[1] in init_chars:
                    tag_info[row[0]] = [0, row[2]]
            else:
                tag_info[row[0]] = [0, row[2]]

        sql = 'SELECT {} FROM MainTable'.format(tag_table)
        self.cursor.execute(sql)
        for row in self.cursor.fetchall():
            item_list = self._convert_list(row[0])
            for il in item_list:
                if il in tag_info.keys():
                    tag_info[il][0] += 1

        return tag_info

    def get_tag_items(self, table, col_name, name=None, convert=False):
        """
        get_itemのタグ用のテーブル版
        """

        if type(col_name) is list:
            sql = 'SELECT {} FROM {}'.format(','.join(col_name), table)
        elif type(col_name) is str:
            sql = 'SELECT {} FROM {}'.format(col_name, table)
        else:
            return []

        if not name is None:
            sql += ' WHERE Name="{}"'.format(name)

        self.logger.debug(sql)
        ret = self.cursor.execute(sql)

        if convert:
            if type(col_name) is list:
                tmp_dict = {}
                for cl in col_name:
                    tmp_dict[cl] = []
                for row in ret.fetchall():
                    for i, cl in enumerate(col_name):
                        tmp_dict[cl].append(self._convert_list(row[i]))
                return tmp_dict
            elif type(col_name) is str:
                tmp_list = []
                for row in ret.fetchall():
                    tmp_list += self._convert_list(row[0])
                return tmp_list
        else:
            return ret.fetchall()

    def tag_backup(self):

        date_str = datetime.datetime.now().strftime('%Y%m%d%H%M')
        save_dir = os.path.join(self.db_root, self.BACKUP_DIR, date_str)

        if not os.path.exists(save_dir):
            os.makedirs(save_dir, exist_ok=True)

        table_list = self.get_additional_table()
        for table in table_list:
            fname = os.path.join(save_dir, table + '.csv')
            with open(fname, "w", encoding=self.FILE_ENCODING) as write_file:
                writer = csv.writer(write_file, lineterminator='\n')
                sql = 'SELECT Name,InitialCharacter,Link,IsFavorite FROM {}'.format(table)
                for row in self.cursor.execute(sql):
                    writer.writerow(list(row))
                    #write_file.write(write_txt)

    def add_tag_batch(self, table, path):
        """
        CSVファイルを使ってタグを追加する.
        [Name, InitialCharacter, Link, IsFavoriteの順]
        """
        success_tags = []

        if not os.path.exists(path):
            return success_tags

        with open(path, "r", encoding=self.FILE_ENCODING) as read_file:
            reader = csv.reader(read_file)

            for row in reader:

                num = len(row)
                if num < 2:
                    continue
                elif (row[0] == '') | (row[1] == ''):
                    continue

                values_dict = {
                    'Name':row[0],
                    'InitialCharacter':row[1],
                    'Link':'' if num < 3 else row[2],
                    'IsFavorite':'' if num < 4 else row[3],
                    'Image':''
                }

                if self.add_tag(table, values_dict):
                    success_tags.append(row[0])

        return success_tags

    def show_all(self):
        sql = 'SELECT * FROM MainTable'
        ret = self.cursor.execute(sql)
        for row in ret.fetchall():
            print(row)

    def _convert_dict4sql(self, input_dict):
        """
        辞書形式のデータをSQLのVALUES文に対応させる。
        プレースホルダー型。
        """

        key_list = []
        tmp_list = []
        values = []

        for key, value in input_dict.items():
            key_list.append(key)
            tmp_list.append('?')

            if type(value) is list:
                values.append(LIST_DELIMITER.join([str(i) for i in value]))
            else:
                values.append(value)

        key_list_str = ','.join(key_list)
        tmp_list_str = ','.join(tmp_list)

        sql = '({0}) values ({1})'.format(key_list_str, tmp_list_str)
        sql_values = tuple(values)

        return sql, sql_values

    def _convert_list(self, src):
        if type(src) is str:
            tmp_list = src.split(LIST_DELIMITER)
            return tmp_list
        else:
            return list([src])

    def _gif_to_zip(self, src_gif, dst_dir, thum_dir):

        if not os.path.exists(dst_dir):
            os.mkdir(dst_dir)

        gif = Image.open(src_gif)
        for i, f in enumerate(ImageSequence.Iterator(gif)):
            if i == 0:
                thum_name = os.path.basename(dst_dir) + '.png'
                thum_path = '{}/{}'.format(thum_dir, thum_name)
                f.save(thum_path)

            img_name = str(i).zfill(3) + ".png"
            img_path = '{}/{}'.format(dst_dir, img_name)
            #print(img_path)
            f.save(img_path)

        """
        thum_name = os.path.basename(dst_dir)
        for f in frames:
            name = '{}/{}{}'.format(thum_dir, thum_name, '.gif')
            f.save(name)
            break
        """
        shutil.make_archive(dst_dir, 'zip', dst_dir)
        shutil.rmtree(dst_dir)


# Copyright 2014
# The Cloudscaling Group, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
logging.getLogger('boto').setLevel(logging.INFO)
logging.getLogger('paramiko').setLevel(logging.WARNING)
LOG = logging.getLogger(__name__)

def detect_new_volume(proc_partitions, proc_partitions_new):
    devices = get_devices(proc_partitions)
    devices_new = get_devices(proc_partitions_new)
    devices_new -= devices
    return devices_new.pop()

def get_devices(proc_partitions):
    devices = set()
    for line in proc_partitions:
        items = [item for item in line.split(' ') if len(item) > 0]
        if len(items) > 0:
            devices.add(items[3])

    return devices


import numpy as np
from copy import deepcopy
from astropy.table import Table

# Structure of incoming data will always consist of time stamps, flux values,
# and exposure times.

# There will be gaps in the data. These gaps can be replaced with either the
# median flux of the time series, or left at 0.

# TERMINOLOGY:
#
#    bins - the evenly-spaced "new" bins that the flux data will be distributed
#           into
#
#    binwidth - the width of the bins, in units of time
#
#    time - the time stamps from the original time series
#
#    flux - the flux data from the original time series
#
#    timeseries - the original time series (time and flux)
#
#    exptime - the exposure time for each frame of the time series
#
#    timestamp_position - the position of the time stamp relative to the
#                         exposure
#
#                   0   - the time stamp is at the beginning of each exposure
#                   0.5 - the time stamp is in the middle of each exposure
#                   1   - the time stamp is at the end of each exposure
#
#
#

def zipper(a, b):
    """
    Combines two arrays, fitting the values of b in between the values of a.
    """
    empty = np.empty((a.size + b.size), dtype=a.dtype)
    empty[0::2], empty[1::2] = a, b
    return empty

def rebin(timeseries, binwidth=None, exptime=None, timestamp_position=0.5,
          median_replace=True):
    """
    Rebin a time series into evenly-spaced bins, the number of which is a power
     of two for fast fourier transform compatibility.

    Parameters
    ----------
    timeseries : numpy array or astropy table
        The time series to be rebinned, consisting of two columns: timestamps
        and fluxes, in that order.
    binwidth : scalar, optional
        The width, in the same units of time as the timeseries, of each bin
        into which the fluxes from the timeseries will be distributed. If not
        provided, it will be inferred from the original time series.
    exptime : scalar, optional
        The exposure time, in the same units of time as the timeseries, of each
        image taken. If not provided, it will be inferred from the original
        time series.
    timestamp_position : scalar, optional
        The position of the time stamp relative to each exposure. Values are
        0 - the time stamp is at the beginning of each exposure; 0.5 - the time
        stamp is in the middle of each exposure; 1 - the time stamp is at the
        end of each exposure.
    median_replace : boolean, optional
        If true, gaps in the timeseries will be replaced with the median flux
        of all the data points.

    Returns
    -------
    a : array
        A two-column numpy array containing timestamps and fluxes in evenly-
        spaced bins. Timestamps indicate the beginning of each bin.
    """

    # FORMAT THE TIME SERIES

    if type(timeseries) != np.ndarray and type(timeseries) != Table:
        print('Error: timeseries data type is not a recognizable format.')
        return
    elif type(timeseries) == Table:
        time = np.array(timeseries.columns[0])
        flux = np.array(timeseries.columns[1])
        timeseries = np.vstack((time, flux)).T

    if np.shape(timeseries)[1] != 2:
        print('Error: input array is of the wrong dimension. Please input as '\
              'a two-column array with time and flux, in that order.')
        return

    # Unpack the timeseries
    time = timeseries[:,0]
    flux = timeseries[:,1]

    # If keyword arguments are unspecified, infer them here
    if exptime is None:
        dt = time[1:] - time[:-1]
        exptime = np.median(dt)

    if binwidth is None:
        dt = time[1:] - time[:-1]
        binwidth = np.median(dt)

    # Shift timestamps to be at beginning of exposures
    starttimes = time - timestamp_position*exptime
    endtimes = starttimes + exptime

    # Barycentered data may have overlapping time bins. Cut off the ends
    overlaps = endtimes[:-1] - starttimes[1:]
    endtimes[np.where(overlaps > 0)] = starttimes[np.where(overlaps > 0)[0]+1]

    # Find gaps in the timeseries
    gaps = starttimes[1:] - endtimes[:-1]
    gaps[np.abs(gaps)<1e-6]=0.0#

    # # Median replace, if set to true.
    median_flux = np.median(flux)
    # if median_replace is True:
    #     gaps = gaps*median_flux/exptime
    # else:
    #     gaps = gaps*0.

    # Inject gap replacements into original data
    gapflag1=np.zeros(len(flux))+1
    gapflag2=np.zeros(len(gaps))+1
    gapflag2[gaps>0]=0
    gapflag=zipper(gapflag1,gapflag2)
    print("gaps [:25]", gaps[:25])
    flux = zipper(flux, gaps)
    print("flux[:50]",flux[:50])
    gapidx=np.where(gapflag==0)
    for i in range(len(gapidx)):
        # flux[gapidx[i]]=((flux[gapidx[i]-1] + flux[gapidx[i]+1])*(flux[gapidx[i]]))/exptime
        # flux[gapidx[i]] = (flux[gapidx[i]]*median_flux)/exptime #global median replacement
        print("gapidx[i]-1",(flux[gapidx[i]-1]),"[gapidx[i]+1]",(flux[gapidx[i]+1]))#5/31
        flux[gapidx[i]]=(flux[gapidx[i]]*(flux[gapidx[i]-1]+flux[gapidx[i]+1]))/(2.*exptime)#5/31
    starttimes = zipper(starttimes, endtimes[:-1])
    endtimes = np.append(starttimes[1:], endtimes[-1])

    # Remove injected replacements where no gap was found
    indices_to_remove = np.where(starttimes == endtimes)
    flux = np.delete(flux, indices_to_remove)
    starttimes = np.delete(starttimes, indices_to_remove)
    endtimes = np.delete(endtimes, indices_to_remove)

    # Create the bins the flux will be redistributed into
    duration = np.max(time) + exptime - np.min(time)
    nbins = 2**np.ceil(np.log10(duration/binwidth)/np.log10(2)).astype(int)
    bins = np.zeros(nbins)

    # Bin start and stop times
    startbins = (starttimes[0] + binwidth*np.linspace(0, nbins-1, nbins))
    endbins = startbins + binwidth

    # Add final gap between last data point and end of the bins
    # flux = np.append(flux, (endbins[-1]-endtimes[-1])*median_flux/exptime)
    flux = np.append(flux, flux[-1])
    # print("flux[:25] after rebin",flux[:25]) #
    starttimes = np.append(starttimes, endtimes[-1])
    endtimes = np.append(endtimes, endbins[-1])

    # Split up timeseries flux into bins
    for i in range(len(flux)):

        j = np.where(startbins <= starttimes[i])[0][-1] # 1st bin left of start
        k = np.where(endbins >= endtimes[i])[0][0] # 1st bin right of end

        width = endtimes[i]-starttimes[i]

        if j == k:
            bins[j] += flux[i]

        if j < k:

            # Left bin
            frac_L = (startbins[j+1] - starttimes[i]) / width
            bins[j] += frac_L*flux[i]

            # Right bin
            frac_R = (endtimes[i] - endbins[k-1]) / width
            bins[k] += frac_R*flux[i]

        if j+1 < k:

            # Middle bins
            n = k - (j+1)
            frac = (endbins[k-1] - startbins[j+1]) / width / n
            bins[j+1:k] += frac*flux[i]

    print("bins[:25]",bins[:25])#
    return np.vstack((startbins, bins)).T


import serial
import json
import os
import random
##from twilio.rest import Client
import time
import requests
import pygame

ser = serial.Serial('COM24', 115200)

print ("connected to: " + ser.portstr)

while True:
    line = ser.readline()
    print("read a line")
    line = line.decode('utf8')
    ##line = line [2:13]
    ##line = line.replace(" ", "")
    line=line.rstrip()
    ##print(line)

    if line.count("#") != 2:
        print (line.count("##"))
        continue
    if line.count("?") != 2:
        continue
    line = line [2:30]
    line = line.replace("?", "")
    ##print(line)

    words = line.split(',')
    if "0.00" in words or len(words) != 5:
        continue

    for word in words:
        print(word)


import json
import os
import sys

import requests

AUTH_URL = "https://accounts.spotify.com/api/token"
TOKEN_RELATIVE_PATH = "spotify/access_data/token.json"
CREDENTIALS_RELATIVE_PATH = "spotify/access_data/credentials.json"

client_id = None
client_secret = None
token = None

def credentialsProvided():
    if os.path.exists(CREDENTIALS_RELATIVE_PATH):
        return True
    return False

def loadCredentials():
    if not credentialsProvided():
        sys.exit()
    with open(CREDENTIALS_RELATIVE_PATH) as f:
        json_credentials = json.load(f)
    global client_id
    global client_secret
    client_id = json_credentials["client_id"]
    client_secret = json_credentials["client_secret"]

def tokenFileSaved():
    if os.path.exists(TOKEN_RELATIVE_PATH):
        return True
    return False

def loadToken():
    global token
    if token is not None:
        return
    if tokenFileSaved():
        with open(TOKEN_RELATIVE_PATH) as f:
            token = json.load(f)["token"]
    else:
        getNewToken()

def requestAuthorization():
    if client_id is None and client_secret is None:
        loadCredentials()
    auth_response = requests.post(AUTH_URL, {
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret,
    })
    return auth_response

def getNewToken():
    response = requestAuthorization()
    response_json = response.json()
    global token
    token = response_json["access_token"]
    saveTokenTnJsonFile()
    return token

def saveTokenTnJsonFile():
    if token is not None:
        with open(TOKEN_RELATIVE_PATH, "w") as f:
            json.dump({"token": token}, f)


from typing import Dict

from .queryset import QuerySet
from json import load
from os.path import exists

class HBOSConfig(object):

    # noinspection PyBroadException
    def __init__(self, configFile):
        config = None
        self._new_install = False
        self._querysets: Dict[str, QuerySet] = dict()
        if exists(configFile):
            try:
                with open(configFile, "r") as f:
                    config = load(f)
                    f.close()
            except:
                config = None
                self._new_install=True
        if config is None:
            self._new_install = True
            return

        for k in config["querysets"]:
            self._querysets[k] = QuerySet(config["querysets"][k])

    @property
    def querysets(self) -> Dict[str, QuerySet]:
        return self._querysets

    @querysets.setter
    def set_querysets(self, value: Dict[str, QuerySet]):
        self._querysets = value

    @property
    def is_new(self) -> bool:
        return self._new_install

    @is_new.setter
    def set_is_new(self, value: bool):
        self._new_install = value


from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponseRedirect
from .forms import ContatosForm
from .models import Contatos

def contatos(request):
    lista = Contatos.objects.order_by('nome')
    return render(request, 'contatos.html', {'lista' : lista})

def menu(request):
    return render(request, 'menu.html')

def criar(request):
    if request.method == 'POST':
        form = ContatosForm(request.POST)
        if form.is_valid():

            agenda = form.save(commit = False)

            agenda.save()
            return redirect('recente')
    else:
        form = ContatosForm()

    return render(request, 'criar.html', {'form' : form})

def detalhe(request, pk):
    agenda = get_object_or_404(Contatos, pk=pk)
    return render(request, 'detalhes.html', {'agenda' : agenda})

def editar(request, pk):
    contato = get_object_or_404(Contatos, pk=pk)
    if request.method == 'POST':
        form = ContatosForm(request.POST, instance=contato)
        if form.is_valid():
            contato.save()
            return redirect('detalhe', pk=contato.pk)
    else:
        form = ContatosForm(instance=contato)
    return render(request, 'editar.html', {'form' : form})

def deletar(request, pk):
    contato = get_object_or_404(Contatos, pk=pk)
    contato.delete()
    return redirect('contatos')

def recente(request):
    recente = Contatos.objects.latest('id')
    return render(request, 'recente.html', {'recente' : recente})


import logging

import discord
from discord.ext import commands
from motor.motor_asyncio import AsyncIOMotorClient

logger = logging.getLogger("DDog")

class DDog(commands.AutoShardedBot):
    def __init__(self, *args, **kwargs):
        if kwargs.get("mongourl"):
            logger.info("URL para MongoDB detectada, intentando conexion")
            self.db = AsyncIOMotorClient(kwargs.get("mongourl"))
        else:
            self.db = None

        if "mongourl" in kwargs:
            kwargs.pop("mongourl")

        super().__init__(*args, **kwargs)

        self.remove_command("help")

        self.add_command(self.stop)
        self.add_command(self._help)

    async def on_command(self, ctx):
        logger.info("Commando por {0.name} - {0.id}: {1.content}".format(ctx.author, ctx.message))

    async def on_ready(self):
        logger.info("Listo para trabajar con el prefijo '{0}'".format(self.command_prefix))

        game = discord.Game("{0}help".format(self.command_prefix))
        await self.change_presence(activity=game)

    @commands.command(alias=["detener", "cerrar"])
    @commands.is_owner()
    async def stop(self, ctx):
        """Detiene al Bot (en el caso de Heroku, lo reinicia)"""
        await ctx.send("Adios!")
        await self.logout()

    @commands.command(name="help")
    async def _help(self, ctx, *, uinput=None):
        """Muestra este mensaje de ayuda"""
        coms = []

        for com in self.commands:
            try:
                await com.can_run(ctx)
            except commands.CommandError:
                pass
            else:
                coms.append(com)

        coms = sorted(coms, key=lambda command: command.name)

        index = 0

        if uinput:
            if uinput.isnumeric():
                uinput = int(uinput)

        ifst = 5 * index
        ilst = 5 * index + 5

        if ifst >= len(coms):
            await ctx.send("La pagina de ayuda no es valida.")
            return
        if ilst > len(coms):
            ilst = len(coms)

        coms = coms[ifst:ilst]

        embed = discord.Embed(title="Comandos de {0}".format(ctx.me.name), colour=0x7FD935)
        for command in coms:
            embed.add_field(name=self.command_prefix + command.name,
                            value=command.help.splitlines()[0], inline=False)

        await ctx.send(embed=embed)


import json
from django.http import HttpResponse
from django.views.generic import View

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
from django.utils.decorators import method_decorator

from ..settings import SCRUFFY_BLOG_SETTINGS
from ..models import Post, PostElement
from ..forms import PostForm, PostElementForm, PostImageForm

class PostDetail(View):
    """ Edit a blog post. """

    base_template = 'admin/base_site.html'
    editor_template = 'scruffy_blog/writing/post_detail.html'
    cancel_url = "admin:scruffy_blog_post_changelist" # By name

    # You may swap these out for proxy models, but do so carefully.
    post_model = Post
    element_model = PostElement

    def __init__(self, *args, **kwargs):
        super(PostDetail, self).__init__(*args, **kwargs)
        self.base_template = kwargs.get('base_template') or self.base_template

    @property
    def blog(self):
        """
        Return a blog if you want to lock the post to it.
        Return `None` and the user will select the blog.
        By default, this does nothing, but you can override it.
        """
        return None

    @property
    def scruffy_cropper_url(self):
        return None

    @method_decorator(login_required)
    @method_decorator(permission_required('blog.change_post'))
    def dispatch(self, *args, **kwargs):
        return super(PostDetail, self).dispatch(*args, **kwargs)

    def get_forms(self):
        self.form = PostForm(self.request.POST or None,
            files=self.request.FILES or None,
            instance=self.post,
            prefix="post-core",
            redirect_url=self.request.path,
            cropper_url=self.scruffy_cropper_url,
            blog=self.blog
            )
        self.embed_form = PostElementForm(post=self.post, ui=True)
        return self.form, self.embed_form

    def get_post(self):
        """ Get the post. """
        obj_id = self.kwargs.get('obj_id')
        if obj_id:
            self.post = get_object_or_404(Post, id=obj_id)
        else:
            self.post = Post()
        return self.post

    def post(self, request, **kwargs):
        self.get_post()
        form, embed_form = self.get_forms()
        if form.is_valid():
            obj = form.save()
            element_forms = self.convert_json_to_elements(request.POST.get('export'), obj, files=request.FILES)

            element_ids = []
            elements = []

            # Save (and keep track of) all the forms.
            for e_form in element_forms:
                if not e_form.is_valid():
                    return HttpResponse(unicode(e_form.errors))
                e_obj = e_form.save()
                elements.append(e_obj)
                element_ids.append(e_obj.id)

            # Wipe out all the unkept embeds
            PostElement.objects.filter(post_id=obj.id).exclude(id__in=element_ids).delete()
            messages.add_message(request, messages.SUCCESS, 'Saved! Keep writing!')
            obj.save()

            if str(obj.id) in request.path:
                return redirect(request.path)
            else:
                # This is a hack, since I really don't know where the edit page lives.
                return redirect("{}../{}".format(request.path, str(obj.id)))
        return self.render()

    def get(self, request, *args, **kwargs):
        self.get_post()
        self.get_forms()
        return self.render()

    def render(self):
        return render(self.request, self.editor_template, {
            'base_template': self.base_template,
            'post': self.post,
            'form': self.form,
            'embed_form': self.embed_form,
            "cancel_url": self.cancel_url,
        })

    def convert_json_to_elements(self, export, post_obj, files={}):
        """
        Turns the json into elements for a post.
        Returns a tuple of text_elements and embed_elements
        """
        elements = []
        if not export:
            return elements
        data = json.loads( export )

        for key, params in data.items():
            instance = PostElement()
            if params.get('obj_id'):
                try:  # Text nodes will always be new. Oh well.
                    instance = PostElement.objects.get(id=params.get('obj_id'), post=post_obj)
                except PostElement.DoesNotExist:
                    pass

            order = int(key.split('_')[-1])
            form_data = {
                'obj_type'      : params.get('obj_type') or 'text',
                'content'       : params.get('content') or '',
                'src'           : params.get('src') or '',
                'post'          : post_obj.id,
                'order'         : order,
                'image'         : params.get('image'),
            }
            bound_form = PostElementForm(form_data, instance=instance, post=post_obj)
            elements.append(bound_form)

        return elements

@login_required
def upload_photo(request, obj_id=None, img_id=None):
    """The awful async iframe uploader."""

    form = PostImageForm(request.POST or None, files=request.FILES or None)
    resp = None

    if request.POST:
        if not obj_id:
            resp = {'errors':'Please save your post before adding images.'}
            resp = json.dumps(resp)
        if obj_id and form.is_valid():
            obj = form.save(commit=False)
            obj.post_id = obj_id
            obj.save()
            resp = {'filename': obj.image.url,
                'thumb': obj.__unicode__(),
                'id': obj.id,
            }
            resp = json.dumps(resp)

    return render(request, 'scruffy_blog/writing/upload_photo.html', {
            'form': form,
            'resp': resp,
            'stylesheet': SCRUFFY_BLOG_SETTINGS['upload_photo_stylesheet'],
        })


# -*- coding: utf-8 -*-
"""
Created on Wed Jun  1 10:34:46 2016

@author: Admin_1
"""

import numpy as np
import matplotlib.pyplot as plt

def pca(x):
    print(x.shape)
    mu = np.mean(x, axis=1)
    print(mu)
    xmf = np.vstack(mu) - x
    xcov = 1/x.shape[1]*np.dot(xmf, np.transpose(xmf))
    w, v = np.linalg.eigh(xcov)
    w_sorted = np.sort(w)[::-1]
    v_sorted = v[:, w_sorted.argsort()]

    return mu, xmf, xcov, w_sorted, v_sorted

num1 = np.array([0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,1,1,1,0,0,1,1,1,1,1,0,0,
             1,1,1,1,1,1,0,0,1,0,1,1,0,0,1,1,1,1,1,0,1,1,0,1,1,1,1,
             0,0,1,0,0,1,1,1,1,1,0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,0,0,
             1,1,1,1,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,
             1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1,
             1,1,1,1,0,1,1,0,1,1,0,1,1,1,1]).reshape((10 ,15))

plt.figure(1)
for i in range(num1.shape[0]):
    plt.subplot(2, 5, i+1)
    # b1 = np.flipud(np.transpose(num[i,:].reshape((5,3))))
    b1 = np.flipud(np.transpose(num1[i, ::-1].reshape((5, 3))))
    plt.imshow(b1.T, origin='lower', cmap=plt.cm.gray_r,
               interpolation='nearest')
    plt.xticks([])
    plt.yticks([])

plt.subplot(2, 5, 3)
plt.title('Ziffernmuster')

mu, xmf, xcov, w, v = pca(np.transpose(num1))

plt.figure(2)
plt.imshow(np.flipud(np.transpose(mu[::-1].reshape((5, 3)))).T, origin='lower',
           cmap=plt.cm.gray_r, interpolation='nearest')
plt.xticks([])
plt.yticks([])
plt.title('Mittelwertbild')

plt.figure(3)
for i in range(v.shape[1]):
    plt.subplot(3, 5, i+1)
    b1 = np.flipud(np.transpose((-1)*v[::-1, i].reshape((5, 3))))
    plt.imshow(b1.T, origin='lower', cmap=plt.cm.gray_r,
               interpolation='nearest')
    plt.xticks([])
    plt.yticks([])
V = v[:, 0:14]
X1 = np.dot(np.transpose(V), xmf)
rec = np.vstack(mu)+np.dot(V, X1)

plt.figure(4)
for i in range(rec.shape[1]):
    plt.subplot(2, 5, i+1)
    # b1 = np.flipud(np.transpose(num[i,:].reshape((5,3))))
    b1 = np.flipud(np.transpose(rec[::-1, i].reshape((5, 3))))
    plt.imshow(b1.T, origin='lower', cmap=plt.cm.gray_r,
               interpolation='nearest')
    plt.xticks([])
    plt.yticks([])


import numpy as np

import tensorflow as tf
import threading
import ConvNet
import time
import os

from six.moves import xrange

BATCH_SIZE = 16

IMAGE_W = 192
IMAGE_H = 256

Z_DIM = 128
IMAGE_CHANNEL = 3
LR = 0.00001         # Learning rate

class celebaBetter:
    def __init__(self, filePath):
        self.file_index = 0
        self.content_index = 0
        self.filePath = filePath
        self.bytestream = open(self.filePath + str(self.file_index)+".bin","br")
        self.nextImage = None
        self.t = None
    def extract_data(self):
        def _load_t():
            self.content_index = self.content_index + BATCH_SIZE
            if self.content_index>=4096:#202599
                self.file_index = self.file_index + 1
                if self.file_index >= 31:
                    self.file_index = 0
                self.bytestream.close()
                self.bytestream = open(self.filePath + str(self.file_index)+".bin","br")
                self.content_index = 0

            buf = self.bytestream.read(BATCH_SIZE * IMAGE_H * IMAGE_W * IMAGE_CHANNEL)
            data = np.frombuffer(buf, dtype=np.uint8).astype(np.float32)
            data = (data) / 256.0 - 0.5
            data = data.reshape(BATCH_SIZE, IMAGE_H, IMAGE_W, IMAGE_CHANNEL)
            self.nextImage = data

        while self.nextImage is None:
            if self.t and self.t.isAlive():
                self.t.join()
                print("glich")
            else:
                self.t = threading.Thread(target=_load_t,args=())
                self.t.start()

        ret = self.nextImage
        self.nextImage = None

        self.t = threading.Thread(target=_load_t,args=())
        self.t.start()

        return ret

CBHR = celebaBetter("F:\\MNIST\\celebaBetter\\")

print("startload")
glist = []
GF = 96             # Dimension of G filters in first conv layer. default [64]
loadFromFile = ConvNet.openEmptyFileR('gan11g.txt')
gfc0 = ConvNet.addlist(glist,ConvNet.FC(inDepth = Z_DIM,outDepth = GF*8*3*4,loadFromFile = loadFromFile))
gdc0 = ConvNet.addlist(glist,ConvNet.DeConv(inDepth = GF*8,outDepth = GF*8,filterSize = 3,loadFromFile = loadFromFile))#4in
gdc1 = ConvNet.addlist(glist,ConvNet.DeConv(inDepth = GF*8,outDepth = GF*4,filterSize = 3,loadFromFile = loadFromFile))#8in
gdc2 = ConvNet.addlist(glist,ConvNet.DeConv(inDepth = GF*4,outDepth = GF*2,filterSize = 5,loadFromFile = loadFromFile))#16in
gdc3 = ConvNet.addlist(glist,ConvNet.DeConv(inDepth = GF*2,outDepth = GF*2,filterSize = 5,loadFromFile = loadFromFile))#32in
gdc4 = ConvNet.addlist(glist,ConvNet.DeConv(inDepth = GF*2,outDepth = GF*1,filterSize = 5,loadFromFile = loadFromFile))#64in
gdc5 = ConvNet.addlist(glist,ConvNet.DeConv(inDepth = GF*1,outDepth = IMAGE_CHANNEL,filterSize = 5,loadFromFile = loadFromFile))#128in
if loadFromFile:loadFromFile.close()

dlist = []
DF = 96             # Dimension of D filters in first conv layer. default [64]
loadFromFile = ConvNet.openEmptyFileR('gan11d.txt')
dcv0 = ConvNet.addlist(dlist,ConvNet.Conv(inDepth = IMAGE_CHANNEL,outDepth = DF*1,filterSize = 5,loadFromFile = loadFromFile))#128out
dcv1 = ConvNet.addlist(dlist,ConvNet.Conv(inDepth = DF*1,outDepth = DF*2,filterSize = 5,loadFromFile = loadFromFile))#64out
dcv2 = ConvNet.addlist(dlist,ConvNet.Conv(inDepth = DF*2,outDepth = DF*2,filterSize = 5,loadFromFile = loadFromFile))#32out
dcv3 = ConvNet.addlist(dlist,ConvNet.Conv(inDepth = DF*2,outDepth = DF*4,filterSize = 5,loadFromFile = loadFromFile))#16out
dcv4 = ConvNet.addlist(dlist,ConvNet.Conv(inDepth = DF*4,outDepth = DF*8,filterSize = 3,loadFromFile = loadFromFile))#8out
dcv5 = ConvNet.addlist(dlist,ConvNet.Conv(inDepth = DF*8,outDepth = DF*8,filterSize = 3,loadFromFile = loadFromFile))#4out
dfc0 = ConvNet.addlist(dlist,ConvNet.FC(inDepth = DF*8*3*4,outDepth = 128,loadFromFile = loadFromFile))
dfc1 = ConvNet.addlist(dlist,ConvNet.FC(inDepth = 128,outDepth = 1,loadFromFile = loadFromFile))
if loadFromFile:loadFromFile.close()
print("endload")
def generator(z):
    _ret = gfc0.getLayer(z, isRelu=True, fixed = False)
    _ret = ConvNet.FC2Conv_Reshape(_ret, 4, 3, GF*8)
    _ret = gdc0.getLayer(_ret, height = 8, width = 6, convStride = 2, isRelu=True, fixed = False)
    _ret = gdc1.getLayer(_ret, height = 16, width = 12, convStride = 2, isRelu=True, fixed = False)
    _ret = gdc2.getLayer(_ret, height = 32, width = 24, convStride = 2, isRelu=True, fixed = False)
    _ret = gdc3.getLayer(_ret, height = 64, width = 48, convStride = 2, isRelu=True, fixed = False)
    _ret = gdc4.getLayer(_ret, height = 128, width = 96, convStride = 2, isRelu=True, fixed = False)
    _ret = gdc5.getLayer(_ret, height = IMAGE_H, width = IMAGE_W, convStride = 2, isRelu=False, fixed = False)
    return _ret

def discriminator(inputT):
    _ret = dcv0.getLayer(inputT, convStride = 2, poolSize = 1,isRelu=True, fixed = False)
    _ret = dcv1.getLayer(_ret, convStride = 2, poolSize = 1,isRelu=True, fixed = False)
    _ret = dcv2.getLayer(_ret, convStride = 2, poolSize = 1,isRelu=True, fixed = False)
    _ret = dcv3.getLayer(_ret, convStride = 2, poolSize = 1,isRelu=True, fixed = False)
    _ret = dcv4.getLayer(_ret, convStride = 2, poolSize = 1,isRelu=True, fixed = False)
    _ret = dcv5.getLayer(_ret, convStride = 2, poolSize = 1,isRelu=True, fixed = False)
    _ret = ConvNet.Conv2FC_Reshape(_ret)
    _ret = dfc0.getLayer(_ret, isRelu=True, fixed = False)
    _ret = dfc1.getLayer(_ret, isRelu=False, fixed = False)
    return _ret

def train():
    ###################
#     loadedimage = extract_data()
#     ConvNet.saveImages(loadedimage, [8, 8], "test0.png")
#     loadedimage = extract_data()
#     ConvNet.saveImages(loadedimage, [8, 8], "test1.png")
#     exit()

#     for idx in xrange(0, 1000000000):
#         loadedimage = extract_data()
#         global file_index
#         global content_index
#         print(str(file_index)+","+str(content_index))
#     exit()

    ###################

    images = tf.placeholder(tf.float32, [BATCH_SIZE, IMAGE_H, IMAGE_W, IMAGE_CHANNEL])
    z = tf.placeholder(tf.float32, [BATCH_SIZE, Z_DIM])

    G = generator(z)
    D_logits  = tf.reduce_mean(discriminator(images))
    D_logits_F = tf.reduce_mean(discriminator(G))

    gen_cost = -D_logits_F
    disc_cost = D_logits_F - D_logits

    alpha = tf.random_uniform(shape=[BATCH_SIZE,1], minval=0.0,maxval=1.0)
    differences = G - images
    differences = tf.reshape(differences,[BATCH_SIZE,-1])
    imagereshape = tf.reshape(images,[BATCH_SIZE,-1])
    interpolates = imagereshape + (alpha*differences)
    interpolates = tf.reshape(interpolates,images.shape)
    gradients = tf.gradients(discriminator(interpolates), [interpolates])[0]
    slopes = tf.sqrt(tf.reduce_sum(tf.square(gradients), reduction_indices=[1]))
    gradient_penalty = tf.reduce_mean((slopes-1.0)**2)

    LAMBDA = 10 # Gradient penalty lambda hyperparameter
    disc_cost += LAMBDA*gradient_penalty

    g_vars = ConvNet.getParam(glist)
    d_vars = ConvNet.getParam(dlist)

    d_optim = tf.train.AdamOptimizer(learning_rate=LR,beta1=0.5,beta2=0.9).minimize(disc_cost, var_list=d_vars)
    g_optim = tf.train.AdamOptimizer(learning_rate=LR,beta1=0.5,beta2=0.9).minimize(gen_cost, var_list=g_vars)

    sess = tf.Session()

    sample_z1 = np.random.uniform(-1, 1, size = (BATCH_SIZE, Z_DIM))
    sample_z2 = np.random.uniform(-1, 1, size = (BATCH_SIZE, Z_DIM))
    #sample_z3 = np.random.uniform(-1, 1, size = (BATCH_SIZE, Z_DIM))
    #sample_z4 = np.random.uniform(-1, 1, size = (BATCH_SIZE, Z_DIM))
    #for i in xrange(0,BATCH_SIZE):
    #    sample_z[i] = np.random.uniform(-(i/BATCH_SIZE), (i/BATCH_SIZE), size = (Z_DIM))

    init = tf.global_variables_initializer()
    sess.run(init)

    start_time = time.time()
    idx = 0
    while True:
        idx = idx + 1
        elapsed_time = time.time() - start_time
        start_time = time.time()

        dt = 0
        df = 0
        for _ in xrange(2):
            batch_z = np.random.uniform(-1, 1, size = (BATCH_SIZE, Z_DIM))
            loadedimage = CBHR.extract_data()
            _,d1,d2 = sess.run([d_optim,D_logits,D_logits_F], feed_dict = {z:batch_z, images:loadedimage})
            dt = dt + d1
            df = df + d2

        print(str(idx)+","+str(CBHR.file_index)+","+str(CBHR.content_index)+","+str(elapsed_time)+","+str(dt)+","+str(df))

        batch_z = np.random.uniform(-1, 1, size = (BATCH_SIZE, Z_DIM))
        sess.run(g_optim, feed_dict = {z: batch_z})

        if idx % 10 == 0:

            sample = np.zeros([BATCH_SIZE*2, IMAGE_H, IMAGE_W, IMAGE_CHANNEL], dtype=np.float32)
            sample[BATCH_SIZE*0:BATCH_SIZE*1] = sess.run(G, feed_dict = {z: sample_z1})
            sample[BATCH_SIZE*1:BATCH_SIZE*2] = sess.run(G, feed_dict = {z: sample_z2})
            #sample[BATCH_SIZE*2:BATCH_SIZE*3] = sess.run(G, feed_dict = {z: sample_z3})
            #sample[BATCH_SIZE*3:BATCH_SIZE*4] = sess.run(G, feed_dict = {z: sample_z4})

            def imgSave(idx,sample):
                ConvNet.saveImages(sample, [4,8], 'out11\\sample_%d.png' % (idx))

            t = threading.Thread(target=imgSave,args=(idx,sample))
            t.start()

        exist = False

        if idx%10 == 0:
            exist = os.path.exists("stop.txt")

        if idx % 2000 == 0 or exist:

            def save(idx, gSaver, dSaver):
                print("start save")
                saveToFile = ConvNet.openEmptyFileW("gan11g"+str(idx)+".txt")
                for item in gSaver:
                    item(saveToFile)
                saveToFile.flush();saveToFile.close()

                saveToFile = ConvNet.openEmptyFileW("gan11d"+str(idx)+".txt")
                for item in dSaver:
                    item(saveToFile)
                saveToFile.flush();saveToFile.close()
                print("end save")

            gSaver = []
            dSaver = []
            for item in glist:
                gSaver.append(item.getSaver(sess))
            for item in dlist:
                dSaver.append(item.getSaver(sess))

            t = threading.Thread(target=save,args=(idx,gSaver, dSaver))
            t.start()

        if exist:
            break

    sess.close()

train()


import json

def fmore_learning_course() :
    course_code=input("강의코드 (예: IB171106, OB0104 ..): ")
    course_name=input("강의명 (예: IOT 빅데이터 실무반): ")
    teacher=input("강사 (예: 이현구): ")
    open_date=input("개강일 (예: 2017-12-28): ")
    close_date=input("종료일 (예: 2018-06-26): ")
    learning_course_list.append(fdic_for_learning_course(close_date,open_date,course_code,course_name,teacher))

def fdic_for_learning_course(close_date,open_date,course_code,course_name,teacher):
    dic={}
    dic["close_date"]=close_date
    dic["open_date"]=open_date
    dic["course_code"]=course_code
    dic["course_name"]=course_name
    dic["teacher"]=teacher
    return dic

def fdic_for_total_course(list_learnig,num_of_learned_course):
    dic={}
    dic["learning_course_info"]=list_learnig
    dic["num_of_course_learned"]=num_of_learned_course
    return dic

def fdic_for_per_std(std_addr,std_id,std_age,std_name,total_course_dic):
    dic={}
    dic["address"] =std_addr
    dic["student_ID"] =std_id
    dic["student_age"] =std_age
    dic["student_name"] =std_name
    dic["total_course_info"] = total_course_dic
    return dic
def fput_std_dic():
    total_course_dic=fdic_for_total_course(learning_course_list,num_of_course_learned)
    std_dic=fdic_for_per_std(std_addr,std_id,std_age,std_name,total_course_dic)
    course_system_list.append(std_dic)
    print(course_system_list)

###############search
def fread_std(i):
    global course_system_list
    print("*학생 ID: "+course_system_list[i].get('student_ID'))
    print("*학생 이름: "+course_system_list[i].get('student_name'))
    print("*나이: "+str(course_system_list[i].get('student_age')))
    print("*주소: "+course_system_list[i].get('address'))
    print("*수강정보: ")
    print(" +과거 수강 횟수: "+str(course_system_list[i].get('total_course_info').get('num_of_course_learned')))
    if len(course_system_list[i].get('total_course_info').get('learning_course_info'))==0:
        print(" +현재 수강 과목: 0 ")
    else :
        for j in range(len(course_system_list[i].get('total_course_info').get('learning_course_info'))):
            print("   강의코드: "+str(course_system_list[i].get('total_course_info').get('learning_course_info')[j].get('course_code')))
            print("   강의 명: "+course_system_list[i].get('total_course_info').get('learning_course_info')[j].get('course_name'))
            print("   강사: "+course_system_list[i].get('total_course_info').get('learning_course_info')[j].get('teacher'))
            print("   개강일: "+course_system_list[i].get('total_course_info').get('learning_course_info')[j].get('open_date'))
            print("   종료일: "+course_system_list[i].get('total_course_info').get('learning_course_info')[j].get('close_date'))

def fread_std_sumary(index) :
    print("복수의 결과가 검색되었습니다.\n  ----- 요약 결과 -----  ")
    for i in index :
        print(">>  학생 ID: "+course_system_list[i].get('student_ID') +", 학생 이름:  "+course_system_list[i].get('student_name'))

def fsearch(parameter) :
    global index
    search=input(str_search)
    for i in range (len(course_system_list)):
        if course_system_list[i].get(parameter).find(search) >=0:
            index.append(i)
            print(index)
    if len(index) ==1 :
        fread_std(index[0])
    if len(index) >=2 :
        fread_std_sumary(index)
    index=[]

def fsearch_on_course(parameter1,parameter2):
    global index
    search=input(str_search)
    for i in range(len(course_system_list)):
        for j in range(len(course_system_list[i].get('total_course_info').get(parameter1))):
            if course_system_list[i].get('total_course_info').get(parameter1)[j].get(parameter2).find(search) >=0 :
                index.append(i)
    for i in index :
        fread_std(i)
    print()
    index=[]

####update
def fupdate_std(parameter):
    global index_from_id
    print("현재값: "+course_system_list[index_from_id].get(parameter))
    change=input("바꾸실 값을 입력하세요: ")
    course_system_list[index_from_id][parameter]=change

def fupdate_course(parameter):
    global index_from_id
    if parameter =="num_of_course_learned":
        print("현재값: "+course_system_list[index_from_id].get('total_course_info').get(parameter))
        change=input("바꾸실 값을 입력하세요: ")
        course_system_list[index_from_id].get('total_course_info')[parameter]=change
    elif parameter !="num_of_course_learned":
        print("현재값 :",end='')
        for j in range(len(course_system_list[index_from_id].get('total_course_info').get('learning_course_info'))) :
            print(course_system_list[index_from_id].get('total_course_info').get('learning_course_info')[j].get(parameter),end='')
        print()
        if len(course_system_list[index_from_id].get('total_course_info').get('learning_course_info')) == 1 :
            change=input("바꾸실 값을 입력하세요: ")
            course_system_list[index_from_id].get('total_course_info').get('learnig_course_info')[0][parameter]=change
        elif len(course_system_list[index_from_id].get('total_course_info').get('learning_course_info')) >1 :
            change_num = int(input("몇 번째 값을 바꾸시겠습니까? (예: 2) : "))
            change_num=-1
            change=input("바꾸실 값을 입력하세요: ")
            course_system_list[index_from_id].get('total_course_info').get('learnig_course_info')[change_num][parameter]=change

path='.'
file='\\ITT_Student.json'
while True :
    try :
        with open(path+file,'r',encoding='utf8') as ITT :
            ITT_data=json.load(ITT)
            course_system_list=ITT_data
        break
    except :
        print("파일을 찾을 수 없습니다. 아래 메뉴를 선택하세요")
        sel_fileerr_num=int(input("1.현재 경로에 새 파일 생성 \n2.경로 변경해서 다시 찾기 "))
        if sel_fileerr_num==1:
            f=open(path+file,'w')
            f.close()
            course_system_list=[]
            break
        elif sel_fileerr_num ==2 :
            path = input("변경된 경로를 입력하세요 (예 : c:\\user\\USER25\\Desktop) :")

str_start_menu ="""1. 학생 정보입력\n2. 학생 정보조회\n3. 학생 정보수정\n4. 학생 정보삭제\n5. 프로그램 종료\n메뉴를 선택하세요: """
str_read_menu ="""아래 메뉴를 선택하세요\n1. 전체 학생정보 조회\n--- 검색 조건 선택 ---\n2. ID검색\n3. 이름 검색\n4. 나이 검색\n5. 주소 검색\n6. 과거 수강 횟수 검색\n7. 현재 강의를 수강중인 학생\n8. 현재 수강중인 강의명\n9. 현재 수강 강사\n10. 이전 메뉴\n메뉴를 선택하세요: """
str_update_menu="1. 학생 이름\n2. 나이\n3. 주소\n4. 과거 수강 횟수\n5. 현재 수강 중인 강의 정보\n0. 이전 메뉴\n메뉴 번호를 입력하세요: "
str_update_munu_5="1. 강의 코드\n2. 강의명\n3. 강사\n4. 개강일\n5. 종료일\n0. 취소\n메뉴 번호를 입력하세요: "
str_delete_menu="삭제 유형을 선택하세요.\n1. 전체 삭제\n2. 현재 수강 중인 특정 과목정보 삭제\n3. 이전 메뉴\n메뉴 번호를 선택하세요: "
str_search="검색어를 입력하세요 :"

std_dic={}
total_course_dic={}
learning_course_list=[]
id_count=len(course_system_list)
index=[]
count=0
while True:
    print(''*20+'<<JSON기반 학생 정보 관리 프로그램 >>')
    sel_start_menu_num=int(input(str_start_menu))
    if sel_start_menu_num==5: #프로그램 종료
        break
    elif sel_start_menu_num==1:  #학생 정보 입력
        id_count+=1
        std_id='ITT{0:0>3}'.format(id_count)
        std_name=input("이름 (예: 홍길동):")
        std_age=input("나이 (예: 29):")
        std_addr=input("주소 (예: 대구광역시 동구 아양로 135): ")
        num_of_course_learned=int(input("과거 수강 횟수 (예: 1): "))
        learning_course_yN=input("현재 수강 과목이 있습니까? (예: y/n): ")
        if learning_course_yN=='y' :
            fmore_learning_course()
        elif learning_course_yN=='n' :
            learning_course_list.append({})
        while True:
            anoter_leaning_course=input("현재 수강하는 과목이 더 있습니까? (y/n): ")
            if anoter_leaning_course=='y':
                fmore_learning_course()
            elif anoter_leaning_course=='n':
                break
        fput_std_dic()
        continue
    elif sel_start_menu_num==2: #학생 정보 조회
        sel_read_menu_num=int(input(str_read_menu))
        if sel_read_menu_num==10 :
            print()
            continue
        elif sel_read_menu_num==1 : #전체 학생정보 조회
            for i in range(len(course_system_list)):
                fread_std(i)
        elif sel_read_menu_num==2 : #id검색
            fsearch("student_ID")
        elif sel_read_menu_num==3 : #이름검색
            fsearch("student_name")
        elif sel_read_menu_num==4 : #나이검색
            fsearch("student_age")
        elif sel_read_menu_num==5 : #주소검색
            fsearch("address")
        elif sel_read_menu_num==6 : #과거 수강횟수검색
            fsearch("num_of_course_learned")
        elif sel_read_menu_num==7 : #현재 강의 수강중인 학생 검색
            for i in range(len(course_system_list)):
                if len(course_system_list[i].get('total_course_info').get('learning_course_info'))!=0:
                    index.append(i)
                print(index)
            print("현재 강의를 수강 중인 학생: " ,end='')
            for idx in index:
                print('['+course_system_list[idx].get('student_name')+']',end='')
            index=[]
            print()
        elif sel_read_menu_num==8 : #현재 운영중인 강의명
            fsearch_on_course("learning_course_info",'course_name')
        elif sel_read_menu_num==9 : #현재 수업진행중인 강사
            fsearch_on_course("learning_course_info",'teacher')
    elif sel_start_menu_num==3: # 학생 정보 변경
        update_std_id_input=input('수정할 학생 ID를 입력하세요: ')
        sel_update_menu_num=int(input(str_update_menu))

        for i in range(len(course_system_list)):
            if course_system_list[i].get('student_ID')==update_std_id_input :
                index_from_id=i

        if sel_update_menu_num==0:  # 이전 메뉴
            print()
            continue
        elif sel_update_menu_num==1: #이름
            fupdate_std('student_name')
        elif sel_update_menu_num==2: #나이
            fupdate_std('student_age')
        elif sel_update_menu_num==3: #주소
            fupdate_std('address')
        elif sel_update_menu_num==4: #과거 수강횟수
            fupdate_course('num_of_course_learned')
        elif sel_update_menu_num==5: #현재 수강 과목
            sel_update_menu_5_num =int(input(str_update_munu_5))
            if sel_update_menu_5_num == 0: continue  #취소
            elif sel_update_menu_5_num==1:
                fupdate_course('course_code')
            elif sel_update_menu_5_num==2:
                fupdate_course('course_name')
            elif sel_update_menu_5_num==3:
                fupdate_course('teacher')
            elif sel_update_menu_5_num==4:
                fupdate_course('open_date')
            elif sel_update_menu_5_num==5:
                fupdate_course('close_date')

    elif sel_start_menu_num==4: #학생 정보 삭제
        del_std_id_input=input("삭제할 학생ID를 입력하세요: ")
        sel_delete_menu_num=int(input(str_delete_menu))

        for i in range(len(course_system_list)):
            if course_system_list[i].get('student_ID')==del_std_id_input :
                index_from_id=i

        if sel_delete_menu_num==1:
            del course_system_list[index_from_id]
        if sel_delete_menu_num==2:
            print("아이디 "+del_std_id_input+", 이름 "+course_system_list[index_from_id].get('student_name')+" 학생의 현재 수강 중인 과목 정보 삭제")
            for j in range(len(course_system_list[index_from_id].get('total_course_info').get('learning_course_info'))) :
                print(course_system_list[index_from_id].get('total_course_info').get('learning_course_info')[j].get('course_name'))
            if len(course_system_list[index_from_id].get('total_course_info').get('learning_course_info')) ==1:
                print('#### '+course_system_list[index_from_id].get('total_course_info').get('learning_course_info')[0].get('course_name')+' 수업이 삭제되었습니다 ####')
                del course_system_list[index_from_id].get('total_course_info').get('learning_course_info')[0]
                print()
            elif course_system_list[index_from_id].get('total_course_info').get('learning_course_info') > 1 :
                del_subject_num_input=int(input('몇 번째 과목을 삭제 하시겠습니까?'))
                del_subject_num_input-=1
                print('#### '+course_system_list[index_from_id].get('total_course_info').get('learning_course_info')[del_subject_num_input].get('course_name')+' 수업이 삭제되었습니다 ####')
                del course_system_list[index_from_id].get('total_course_info').get('learning_course_info')[del_subject_num_input]

        if sel_delete_menu_num==3:
            print()
            continue
        pass
with open(path+file,'w',encoding='utf8') as outfile :
    retJson= json.dumps(course_system_list,indent=4,sort_keys=True,ensure_ascii=False)
    outfile.write(retJson)

05_졸업작품/학생_정보_시스템/학생정보 시스템(딕셔너리 중첩)_ver2.py

학생정보 시스템(딕셔너리 중첩)_ver2.py