Implemented YouTube layer, as well as synchronization on schedule. TODO: fix issue where ready() is called twice. The synchronization should only run on one thread/process.

This commit is contained in:
Tiberiu Chibici 2018-10-08 03:01:35 +03:00
parent c26732d101
commit 291da16461
19 changed files with 1958 additions and 284 deletions

11
.idea/dataSources.local.xml generated Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal">
<data-source name="Django default" uuid="2dac2136-d902-4d27-8789-9371934602fd">
<database-info product="SQLite" version="3.20.1" jdbc-version="2.1" driver-name="SQLite JDBC" driver-version="3.20.1.1" family="SQLITE" exact-version="3.20.1" />
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
<auth-required>false</auth-required>
<introspection-schemas>*:@</introspection-schemas>
</data-source>
</component>
</project>

16
.idea/dataSources.xml generated Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Django default" uuid="2dac2136-d902-4d27-8789-9371934602fd">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/YtManager/settings.py</remarks>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:D:\Dev\youtube-channel-manager\db.sqlite3</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
</data-source>
</component>
</project>

View File

@ -0,0 +1,741 @@
<?xml version="1.0" encoding="UTF-8"?>
<dataSource name="Django default">
<database-model serializer="dbm" rdbms="SQLITE" format-version="4.10">
<root id="1">
<ServerVersion>3.20.1</ServerVersion>
</root>
<schema id="2" parent="1" name="main">
<Current>1</Current>
<Visible>1</Visible>
</schema>
<collation id="3" parent="1" name="BINARY"/>
<collation id="4" parent="1" name="NOCASE"/>
<collation id="5" parent="1" name="RTRIM"/>
<table id="6" parent="2" name="YtManagerApp_channel"/>
<table id="7" parent="2" name="YtManagerApp_subscription"/>
<table id="8" parent="2" name="YtManagerApp_subscriptionfolder"/>
<table id="9" parent="2" name="YtManagerApp_video"/>
<table id="10" parent="2" name="auth_group"/>
<table id="11" parent="2" name="auth_group_permissions"/>
<table id="12" parent="2" name="auth_permission"/>
<table id="13" parent="2" name="auth_user"/>
<table id="14" parent="2" name="auth_user_groups"/>
<table id="15" parent="2" name="auth_user_user_permissions"/>
<table id="16" parent="2" name="django_admin_log"/>
<table id="17" parent="2" name="django_content_type"/>
<table id="18" parent="2" name="django_migrations"/>
<table id="19" parent="2" name="django_session"/>
<table id="20" parent="2" name="sqlite_master">
<System>1</System>
</table>
<table id="21" parent="2" name="sqlite_sequence">
<System>1</System>
</table>
<column id="22" parent="6" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="23" parent="6" name="channel_id">
<Position>2</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="24" parent="6" name="username">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="25" parent="6" name="custom_url">
<Position>4</Position>
<DataType>text|0s</DataType>
</column>
<column id="26" parent="6" name="name">
<Position>5</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="27" parent="6" name="description">
<Position>6</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="28" parent="6" name="icon_default">
<Position>7</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="29" parent="6" name="icon_best">
<Position>8</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="30" parent="6" name="upload_playlist_id">
<Position>9</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="31" parent="6" name="sqlite_autoindex_YtManagerApp_channel_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>channel_id</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="32" parent="6" name="sqlite_autoindex_YtManagerApp_channel_2">
<NameSurrogate>1</NameSurrogate>
<ColNames>username</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="33" parent="6" name="sqlite_autoindex_YtManagerApp_channel_3">
<NameSurrogate>1</NameSurrogate>
<ColNames>custom_url</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="34" parent="6">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<key id="35" parent="6">
<ColNames>channel_id</ColNames>
<UnderlyingIndexName>sqlite_autoindex_YtManagerApp_channel_1</UnderlyingIndexName>
</key>
<key id="36" parent="6">
<ColNames>username</ColNames>
<UnderlyingIndexName>sqlite_autoindex_YtManagerApp_channel_2</UnderlyingIndexName>
</key>
<key id="37" parent="6">
<ColNames>custom_url</ColNames>
<UnderlyingIndexName>sqlite_autoindex_YtManagerApp_channel_3</UnderlyingIndexName>
</key>
<column id="38" parent="7" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="39" parent="7" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="40" parent="7" name="parent_folder_id">
<Position>3</Position>
<DataType>integer|0s</DataType>
</column>
<column id="41" parent="7" name="playlist_id">
<Position>4</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="42" parent="7" name="description">
<Position>5</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="43" parent="7" name="icon_best">
<Position>6</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="44" parent="7" name="icon_default">
<Position>7</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="45" parent="7" name="channel_id">
<Position>8</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="46" parent="7" name="sqlite_autoindex_YtManagerApp_subscription_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>playlist_id</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="47" parent="7" name="YtManagerApp_subscription_parent_folder_id_c4c64c21">
<ColNames>parent_folder_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="48" parent="7" name="YtManagerApp_subscription_channel_id_b83c6f21">
<ColNames>channel_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="49" parent="7">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<key id="50" parent="7">
<ColNames>playlist_id</ColNames>
<UnderlyingIndexName>sqlite_autoindex_YtManagerApp_subscription_1</UnderlyingIndexName>
</key>
<foreign-key id="51" parent="7">
<ColNames>parent_folder_id</ColNames>
<RefTableName>YtManagerApp_subscriptionfolder</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<foreign-key id="52" parent="7">
<ColNames>channel_id</ColNames>
<RefTableName>YtManagerApp_channel</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="53" parent="8" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="54" parent="8" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="55" parent="8" name="parent_id">
<Position>3</Position>
<DataType>integer|0s</DataType>
</column>
<index id="56" parent="8" name="YtManagerApp_subscriptionfolder_parent_id_bd5f4bc1">
<ColNames>parent_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="57" parent="8">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<foreign-key id="58" parent="8">
<ColNames>parent_id</ColNames>
<RefTableName>YtManagerApp_subscriptionfolder</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="59" parent="9" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="60" parent="9" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="61" parent="9" name="video_id">
<Position>3</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="62" parent="9" name="downloaded_path">
<Position>4</Position>
<DataType>text|0s</DataType>
</column>
<column id="63" parent="9" name="watched">
<Position>5</Position>
<DataType>bool|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="64" parent="9" name="subscription_id">
<Position>6</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="65" parent="9" name="description">
<Position>7</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="66" parent="9" name="icon_best">
<Position>8</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="67" parent="9" name="icon_default">
<Position>9</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="68" parent="9" name="playlist_index">
<Position>10</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="69" parent="9" name="publish_date">
<Position>11</Position>
<DataType>datetime|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="70" parent="9" name="YtManagerApp_video_subscription_id_720d4227">
<ColNames>subscription_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="71" parent="9">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<foreign-key id="72" parent="9">
<ColNames>subscription_id</ColNames>
<RefTableName>YtManagerApp_subscription</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="73" parent="10" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="74" parent="10" name="name">
<Position>2</Position>
<DataType>varchar(80)|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="75" parent="10" name="sqlite_autoindex_auth_group_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>name</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="76" parent="10">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<key id="77" parent="10">
<ColNames>name</ColNames>
<UnderlyingIndexName>sqlite_autoindex_auth_group_1</UnderlyingIndexName>
</key>
<column id="78" parent="11" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="79" parent="11" name="group_id">
<Position>2</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="80" parent="11" name="permission_id">
<Position>3</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="81" parent="11" name="auth_group_permissions_group_id_permission_id_0cd325b0_uniq">
<ColNames>group_id
permission_id</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="82" parent="11" name="auth_group_permissions_group_id_b120cbf9">
<ColNames>group_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="83" parent="11" name="auth_group_permissions_permission_id_84c5c92e">
<ColNames>permission_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="84" parent="11">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<foreign-key id="85" parent="11">
<ColNames>group_id</ColNames>
<RefTableName>auth_group</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<foreign-key id="86" parent="11">
<ColNames>permission_id</ColNames>
<RefTableName>auth_permission</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="87" parent="12" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="88" parent="12" name="content_type_id">
<Position>2</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="89" parent="12" name="codename">
<Position>3</Position>
<DataType>varchar(100)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="90" parent="12" name="name">
<Position>4</Position>
<DataType>varchar(255)|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="91" parent="12" name="auth_permission_content_type_id_codename_01ab375a_uniq">
<ColNames>content_type_id
codename</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="92" parent="12" name="auth_permission_content_type_id_2f476e4b">
<ColNames>content_type_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="93" parent="12">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<foreign-key id="94" parent="12">
<ColNames>content_type_id</ColNames>
<RefTableName>django_content_type</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="95" parent="13" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="96" parent="13" name="password">
<Position>2</Position>
<DataType>varchar(128)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="97" parent="13" name="last_login">
<Position>3</Position>
<DataType>datetime|0s</DataType>
</column>
<column id="98" parent="13" name="is_superuser">
<Position>4</Position>
<DataType>bool|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="99" parent="13" name="username">
<Position>5</Position>
<DataType>varchar(150)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="100" parent="13" name="first_name">
<Position>6</Position>
<DataType>varchar(30)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="101" parent="13" name="email">
<Position>7</Position>
<DataType>varchar(254)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="102" parent="13" name="is_staff">
<Position>8</Position>
<DataType>bool|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="103" parent="13" name="is_active">
<Position>9</Position>
<DataType>bool|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="104" parent="13" name="date_joined">
<Position>10</Position>
<DataType>datetime|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="105" parent="13" name="last_name">
<Position>11</Position>
<DataType>varchar(150)|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="106" parent="13" name="sqlite_autoindex_auth_user_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>username</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="107" parent="13">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<key id="108" parent="13">
<ColNames>username</ColNames>
<UnderlyingIndexName>sqlite_autoindex_auth_user_1</UnderlyingIndexName>
</key>
<column id="109" parent="14" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="110" parent="14" name="user_id">
<Position>2</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="111" parent="14" name="group_id">
<Position>3</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="112" parent="14" name="auth_user_groups_user_id_group_id_94350c0c_uniq">
<ColNames>user_id
group_id</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="113" parent="14" name="auth_user_groups_user_id_6a12ed8b">
<ColNames>user_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="114" parent="14" name="auth_user_groups_group_id_97559544">
<ColNames>group_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="115" parent="14">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<foreign-key id="116" parent="14">
<ColNames>user_id</ColNames>
<RefTableName>auth_user</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<foreign-key id="117" parent="14">
<ColNames>group_id</ColNames>
<RefTableName>auth_group</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="118" parent="15" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="119" parent="15" name="user_id">
<Position>2</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="120" parent="15" name="permission_id">
<Position>3</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="121" parent="15" name="auth_user_user_permissions_user_id_permission_id_14a6b632_uniq">
<ColNames>user_id
permission_id</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="122" parent="15" name="auth_user_user_permissions_user_id_a95ead1b">
<ColNames>user_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="123" parent="15" name="auth_user_user_permissions_permission_id_1fbb5f2c">
<ColNames>permission_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="124" parent="15">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<foreign-key id="125" parent="15">
<ColNames>user_id</ColNames>
<RefTableName>auth_user</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<foreign-key id="126" parent="15">
<ColNames>permission_id</ColNames>
<RefTableName>auth_permission</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="127" parent="16" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="128" parent="16" name="action_time">
<Position>2</Position>
<DataType>datetime|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="129" parent="16" name="object_id">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="130" parent="16" name="object_repr">
<Position>4</Position>
<DataType>varchar(200)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="131" parent="16" name="change_message">
<Position>5</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="132" parent="16" name="content_type_id">
<Position>6</Position>
<DataType>integer|0s</DataType>
</column>
<column id="133" parent="16" name="user_id">
<Position>7</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="134" parent="16" name="action_flag">
<Position>8</Position>
<DataType>smallint unsigned|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="135" parent="16" name="django_admin_log_content_type_id_c4bce8eb">
<ColNames>content_type_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="136" parent="16" name="django_admin_log_user_id_c564eba6">
<ColNames>user_id</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="137" parent="16">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<foreign-key id="138" parent="16">
<ColNames>content_type_id</ColNames>
<RefTableName>django_content_type</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<foreign-key id="139" parent="16">
<ColNames>user_id</ColNames>
<RefTableName>auth_user</RefTableName>
<RefColNames>id</RefColNames>
<Deferrable>1</Deferrable>
<InitiallyDeferred>1</InitiallyDeferred>
</foreign-key>
<column id="140" parent="17" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="141" parent="17" name="app_label">
<Position>2</Position>
<DataType>varchar(100)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="142" parent="17" name="model">
<Position>3</Position>
<DataType>varchar(100)|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="143" parent="17" name="django_content_type_app_label_model_76bd3d3b_uniq">
<ColNames>app_label
model</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="144" parent="17">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<column id="145" parent="18" name="id">
<Position>1</Position>
<DataType>integer|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="146" parent="18" name="app">
<Position>2</Position>
<DataType>varchar(255)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="147" parent="18" name="name">
<Position>3</Position>
<DataType>varchar(255)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="148" parent="18" name="applied">
<Position>4</Position>
<DataType>datetime|0s</DataType>
<NotNull>1</NotNull>
</column>
<key id="149" parent="18">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
<column id="150" parent="19" name="session_key">
<Position>1</Position>
<DataType>varchar(40)|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="151" parent="19" name="session_data">
<Position>2</Position>
<DataType>text|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="152" parent="19" name="expire_date">
<Position>3</Position>
<DataType>datetime|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="153" parent="19" name="sqlite_autoindex_django_session_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>session_key</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="154" parent="19" name="django_session_expire_date_a5c62663">
<ColNames>expire_date</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="155" parent="19">
<ColNames>session_key</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_django_session_1</UnderlyingIndexName>
</key>
<column id="156" parent="20" name="type">
<Position>1</Position>
<DataType>text|0s</DataType>
</column>
<column id="157" parent="20" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
</column>
<column id="158" parent="20" name="tbl_name">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="159" parent="20" name="rootpage">
<Position>4</Position>
<DataType>integer|0s</DataType>
</column>
<column id="160" parent="20" name="sql">
<Position>5</Position>
<DataType>text|0s</DataType>
</column>
<column id="161" parent="21" name="name">
<Position>1</Position>
</column>
<column id="162" parent="21" name="seq">
<Position>2</Position>
</column>
</database-model>
</dataSource>

View File

@ -0,0 +1,2 @@
#n:main
!<md> [0, 0, null, null, -2147483648, -2147483648]

501
.idea/workspace.xml generated
View File

@ -2,20 +2,21 @@
<project version="4"> <project version="4">
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="245751b6-c863-4572-8723-8499964fe105" name="Default Changelist" comment=""> <list default="true" id="245751b6-c863-4572-8723-8499964fe105" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" /> <change afterPath="$PROJECT_DIR$/.idea/dataSources.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/watcherTasks.xml" afterDir="false" /> <change afterPath="$PROJECT_DIR$/YtManagerApp/migrations/0005_auto_20181007_2015.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/management.py" afterDir="false" /> <change afterPath="$PROJECT_DIR$/YtManagerApp/migrations/0006_auto_20181008_0037.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/migrations/0004_auto_20181005_1626.py" afterDir="false" /> <change afterPath="$PROJECT_DIR$/YtManagerApp/youtube.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css.map" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss" afterDir="false" /> <change beforePath="$PROJECT_DIR$/YtManager/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManager/settings.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManager/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManager/urls.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/YtManager/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManager/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" afterDir="false" /> <change beforePath="$PROJECT_DIR$/YtManagerApp/admin.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/admin.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.js" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.js" afterDir="false" /> <change beforePath="$PROJECT_DIR$/YtManagerApp/apps.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/apps.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/js/subtree.js" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/YtManagerApp/management.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/management.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/views.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/YtManagerApp/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/views.py" afterDir="false" />
</list> </list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
@ -24,6 +25,36 @@
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" /> <option name="LAST_RESOLUTION" value="IGNORE" />
</component> </component>
<component name="DatabaseView">
<option name="SHOW_INTERMEDIATE" value="true" />
<option name="GROUP_DATA_SOURCES" value="true" />
<option name="GROUP_SCHEMA" value="true" />
<option name="GROUP_CONTENTS" value="false" />
<option name="SORT_POSITIONED" value="false" />
<option name="SHOW_EMPTY_GROUPS" value="false" />
<option name="AUTO_SCROLL_FROM_SOURCE" value="false" />
<option name="HIDDEN_KINDS">
<set />
</option>
<expand>
<path>
<item name="Database" type="3277223f:DatabaseStructure$DbRootGroup" />
<item name="Django default" type="feb32156:DbDataSourceImpl" />
</path>
<path>
<item name="Database" type="3277223f:DatabaseStructure$DbRootGroup" />
<item name="Django default" type="feb32156:DbDataSourceImpl" />
<item name="schemas" type="d4e8921:DatabaseStructure$FamilyGroup" />
</path>
<path>
<item name="Database" type="3277223f:DatabaseStructure$DbRootGroup" />
<item name="Django default" type="feb32156:DbDataSourceImpl" />
<item name="schemas" type="d4e8921:DatabaseStructure$FamilyGroup" />
<item name="main: schema" type="90513b60:SqliteImplModel$Schema" />
</path>
</expand>
<select />
</component>
<component name="DjangoConsoleOptions" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)"> <component name="DjangoConsoleOptions" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)">
<option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" /> <option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" />
</component> </component>
@ -31,63 +62,74 @@
<session id="1684258556"> <session id="1684258556">
<usages-collector id="statistics.lifecycle.project"> <usages-collector id="statistics.lifecycle.project">
<counts> <counts>
<entry key="project.closed" value="1" /> <entry key="project.closed" value="2" />
<entry key="project.open.time.1" value="1" />
<entry key="project.open.time.11" value="1" /> <entry key="project.open.time.11" value="1" />
<entry key="project.open.time.3" value="1" /> <entry key="project.open.time.3" value="1" />
<entry key="project.opened" value="2" /> <entry key="project.opened" value="3" />
</counts> </counts>
</usages-collector> </usages-collector>
<usages-collector id="statistics.file.extensions.edit"> <usages-collector id="statistics.file.extensions.edit">
<counts> <counts>
<entry key="Django Console" value="65" />
<entry key="css" value="40" /> <entry key="css" value="40" />
<entry key="html" value="620" /> <entry key="dummy" value="2" />
<entry key="js" value="2858" /> <entry key="html" value="747" />
<entry key="js" value="4689" />
<entry key="less" value="38" /> <entry key="less" value="38" />
<entry key="py" value="1710" /> <entry key="py" value="11289" />
<entry key="py@youtube-channel-manager" value="19" /> <entry key="py@youtube-channel-manager" value="95" />
<entry key="scss" value="26" /> <entry key="scss" value="26" />
</counts> </counts>
</usages-collector> </usages-collector>
<usages-collector id="statistics.file.types.edit"> <usages-collector id="statistics.file.types.edit">
<counts> <counts>
<entry key="CSS" value="40" /> <entry key="CSS" value="40" />
<entry key="CommandLine" value="19" /> <entry key="CommandLine" value="39" />
<entry key="HTML" value="620" /> <entry key="HTML" value="747" />
<entry key="JavaScript" value="2858" /> <entry key="JavaScript" value="4689" />
<entry key="Less" value="38" /> <entry key="Less" value="38" />
<entry key="Python" value="1710" /> <entry key="PLAIN_TEXT" value="58" />
<entry key="Python" value="11354" />
<entry key="SCSS" value="26" /> <entry key="SCSS" value="26" />
</counts> </counts>
</usages-collector> </usages-collector>
<usages-collector id="statistics.file.extensions.open"> <usages-collector id="statistics.file.extensions.open">
<counts> <counts>
<entry key="css" value="1" /> <entry key="css" value="1" />
<entry key="html" value="8" /> <entry key="html" value="17" />
<entry key="js" value="5" /> <entry key="js" value="5" />
<entry key="less" value="1" /> <entry key="less" value="1" />
<entry key="py" value="7" /> <entry key="py" value="26" />
<entry key="scss" value="3" /> <entry key="scss" value="3" />
<entry key="ytmanagerapp_channel" value="1" />
<entry key="ytmanagerapp_subscription" value="1" />
<entry key="ytmanagerapp_subscriptionfolder" value="1" />
<entry key="ytmanagerapp_video" value="2" />
</counts> </counts>
</usages-collector> </usages-collector>
<usages-collector id="statistics.file.types.open"> <usages-collector id="statistics.file.types.open">
<counts> <counts>
<entry key="CSS" value="1" /> <entry key="CSS" value="1" />
<entry key="HTML" value="8" /> <entry key="Database Element" value="5" />
<entry key="HTML" value="17" />
<entry key="JavaScript" value="5" /> <entry key="JavaScript" value="5" />
<entry key="Less" value="1" /> <entry key="Less" value="1" />
<entry key="Python" value="7" /> <entry key="Python" value="26" />
<entry key="SCSS" value="3" /> <entry key="SCSS" value="3" />
</counts> </counts>
</usages-collector> </usages-collector>
</session> </session>
</component> </component>
<component name="FileEditorManager"> <component name="FileEditorManager">
<splitter split-orientation="horizontal" split-proportion="0.82295084">
<split-first>
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300"> <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file pinned="false" current-in-tab="false"> <file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/views.py"> <entry file="file://$PROJECT_DIR$/YtManagerApp/views.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="334"> <state relative-caret-position="469">
<caret line="62" lean-forward="true" selection-start-line="62" selection-end-line="62" /> <caret line="87" lean-forward="true" selection-start-line="87" selection-end-line="87" />
<folding> <folding>
<element signature="e#0#35#0" expanded="true" /> <element signature="e#0#35#0" expanded="true" />
</folding> </folding>
@ -96,46 +138,120 @@
</entry> </entry>
</file> </file>
<file pinned="false" current-in-tab="false"> <file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManager/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="289">
<caret line="17" selection-start-line="17" selection-end-line="17" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/YtManagerApp/management.py"> <entry file="file://$PROJECT_DIR$/YtManagerApp/management.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504"> <state relative-caret-position="208">
<caret line="42" column="11" selection-start-line="42" selection-start-column="11" selection-end-line="42" selection-end-column="11" /> <caret line="183" column="47" selection-start-line="183" selection-start-column="47" selection-end-line="183" selection-end-column="47" />
<folding> <folding>
<marker date="1538766618853" expanded="true" signature="90:91" ph="..." /> <element signature="e#0#68#0" expanded="true" />
<marker date="1538766618853" expanded="true" signature="642:651" ph="..." /> <marker date="1538954367974" expanded="true" signature="819:828" ph="..." />
<marker date="1538954367974" expanded="true" signature="1597:1603" ph="..." />
<marker date="1538954367974" expanded="true" signature="4940:6762" ph="..." />
<marker date="1538954367974" expanded="true" signature="5447:5983" ph="..." />
<marker date="1538954367974" expanded="true" signature="6067:6076" ph="..." />
<marker date="1538954367974" expanded="true" signature="6067:6496" ph="..." />
</folding> </folding>
</state> </state>
</provider> </provider>
</entry> </entry>
</file> </file>
<file pinned="false" current-in-tab="false"> <file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js"> <entry file="file://$PROJECT_DIR$/YtManagerApp/__init__.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor" />
<state relative-caret-position="1700">
<caret line="100" column="186" selection-start-line="100" selection-start-column="186" selection-end-line="100" selection-end-column="186" />
</state>
</provider>
</entry> </entry>
</file> </file>
<file pinned="false" current-in-tab="true"> <file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html"> <entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357"> <state relative-caret-position="438">
<caret line="21" column="30" lean-forward="true" selection-start-line="21" selection-start-column="30" selection-end-line="21" selection-end-column="30" /> <caret line="338" column="3" selection-start-line="338" selection-start-column="3" selection-end-line="338" selection-end-column="3" />
<folding>
<element signature="e#433#440#1#HTML" expanded="true" />
</folding>
</state> </state>
</provider> </provider>
</entry> </entry>
</file> </file>
</leaf> </leaf>
</split-first>
<split-second>
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="340">
<caret line="20" column="29" selection-start-line="20" selection-start-column="29" selection-end-line="20" selection-end-column="29" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="13" column="33" selection-start-line="13" selection-start-column="33" selection-end-line="13" selection-end-column="33" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="138">
<caret line="22" column="45" selection-start-line="22" selection-start-column="45" selection-end-line="22" selection-end-column="45" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/YtManagerApp/youtube.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="354">
<caret line="206" column="24" selection-start-line="206" selection-start-column="24" selection-end-line="206" selection-end-column="24" />
<folding>
<element signature="e#0#43#0" expanded="true" />
<marker date="1538948346076" expanded="true" signature="2071:2953" ph="..." />
<marker date="1538948346076" expanded="true" signature="5760:6122" ph="..." />
<marker date="1538948346076" expanded="true" signature="5843:6122" ph="..." />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="377">
<caret line="24" selection-start-line="24" selection-end-line="24" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="439">
<caret line="70" lean-forward="true" selection-start-line="70" selection-end-line="70" />
</state>
</provider>
</entry>
</file>
</leaf>
</split-second>
</splitter>
</component> </component>
<component name="FileTemplateManagerImpl"> <component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="SCSS File" /> <option value="SCSS File" />
<option value="Python Script" /> <option value="Python Script" />
<option value="Setup Script" />
</list> </list>
</option> </option>
</component> </component>
@ -146,12 +262,21 @@
<find>url</find> <find>url</find>
<find>delete_fo</find> <find>delete_fo</find>
<find>folder</find> <find>folder</find>
<find>modal</find>
<find>folderEditDialog</find>
<find>subscriptionEditDialog_</find>
<find>folder_edit_dialog</find>
<find>folderEditDialog_</find>
<find>treeNode_Edit</find>
<find>default_app_config</find>
<find>start_synchronization_timer</find>
</findStrings> </findStrings>
<replaceStrings> <replaceStrings>
<replace>loading</replace> <replace>loading</replace>
<replace /> <replace />
<replace>subscription</replace> <replace>subscription</replace>
<replace>subscription</replace> <replace>subscriptionEditDialog</replace>
<replace>folderEditDialog</replace>
</replaceStrings> </replaceStrings>
</component> </component>
<component name="Git.Settings"> <component name="Git.Settings">
@ -165,14 +290,20 @@
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html" /> <option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js" /> <option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss" /> <option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" />
<option value="$PROJECT_DIR$/YtManagerApp/views.py" />
<option value="$PROJECT_DIR$/YtManagerApp/management.py" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/js/subscription_tree.js" /> <option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/js/subscription_tree.js" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" /> <option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" />
<option value="$PROJECT_DIR$/YtManager/urls.py" /> <option value="$PROJECT_DIR$/YtManager/urls.py" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" /> <option value="$PROJECT_DIR$/YtManagerApp/views.py" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" /> <option value="$PROJECT_DIR$/YtManagerApp/admin.py" />
<option value="$PROJECT_DIR$/YtManagerApp/__init__.py" />
<option value="$PROJECT_DIR$/YtManager/settings.py" />
<option value="$PROJECT_DIR$/YtManagerApp/apps.py" />
<option value="$PROJECT_DIR$/YtManagerApp/models.py" />
<option value="$PROJECT_DIR$/YtManagerApp/youtube.py" />
<option value="$PROJECT_DIR$/YtManagerApp/management.py" />
</list> </list>
</option> </option>
</component> </component>
@ -183,10 +314,9 @@
<sorting>DEFINITION_ORDER</sorting> <sorting>DEFINITION_ORDER</sorting>
</component> </component>
<component name="ProjectFrameBounds" extendedState="6"> <component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="-8" /> <option name="x" value="1902" />
<option name="y" value="1" /> <option name="width" value="1953" />
<option name="width" value="1400" /> <option name="height" value="2058" />
<option name="height" value="1000" />
</component> </component>
<component name="ProjectView"> <component name="ProjectView">
<navigator proportions="" version="1"> <navigator proportions="" version="1">
@ -210,48 +340,6 @@
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" /> <item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" /> <item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path> </path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="static" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="static" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="controls" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="js" type="462c0819:PsiDirectoryNode" />
</path>
</expand> </expand>
<select /> <select />
</subPane> </subPane>
@ -262,11 +350,12 @@
<component name="PropertiesComponent"> <component name="PropertiesComponent">
<property name="SearchEverywhereHistoryKey" value="&#9;FILE&#9;file://D:/Dev/youtube-channel-manager/YtManagerApp/views.py" /> <property name="SearchEverywhereHistoryKey" value="&#9;FILE&#9;file://D:/Dev/youtube-channel-manager/YtManagerApp/views.py" />
<property name="WebServerToolWindowFactoryState" value="false" /> <property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$USER_HOME$/AppData/Roaming/npm/lessc.cmd" /> <property name="database.console.LAST_STATE" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/../YoutubeApi-tests/get_playlist_info.py" />
<property name="list.type.of.created.stylesheet" value="SCSS" /> <property name="list.type.of.created.stylesheet" value="SCSS" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" /> <property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" /> <property name="nodejs_npm_path_reset_for_default_project" value="true" />
<property name="settings.editor.selected.configurable" value="preferences.sourceCode" /> <property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
@ -328,9 +417,34 @@
<servers /> <servers />
</component> </component>
<component name="ToolWindowManager"> <component name="ToolWindowManager">
<frame x="-8" y="-8" width="1936" height="1056" extended-state="6" /> <frame x="-6" y="-6" width="1292" height="692" extended-state="6" />
<editor active="true" /> <editor active="true" />
<layout> <layout>
<window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.25970873" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="Favorites" order="2" side_tool="true" />
<window_info anchor="bottom" id="Database Console" sideWeight="0.49838188" weight="0.3285968" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" sideWeight="0.4989339" weight="0.329718" />
<window_info active="true" anchor="bottom" id="Run" order="2" sideWeight="0.49573562" visible="true" weight="0.3090586" />
<window_info anchor="bottom" id="Debug" order="3" sideWeight="0.49946696" weight="0.3991323" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="manage.py@youtube-channel-manager" order="7" sideWeight="0.49840087" weight="0.3286334" />
<window_info anchor="bottom" id="Docker" order="8" show_stripe_button="false" />
<window_info anchor="bottom" id="Database Changes" order="9" />
<window_info anchor="bottom" id="Event Log" order="10" sideWeight="0.50161815" side_tool="true" weight="0.5399645" />
<window_info anchor="bottom" id="Version Control" order="11" weight="0.329718" />
<window_info anchor="bottom" id="Terminal" order="12" sideWeight="0.49733475" weight="0.35791758" />
<window_info anchor="bottom" id="Python Console" order="13" sideWeight="0.49840087" weight="0.3286334" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="SciView" order="3" />
<window_info anchor="right" id="Database" order="4" weight="0.31634304" />
</layout>
<layout-to-restore>
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.18017058" /> <window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.18017058" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" /> <window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="Favorites" order="2" side_tool="true" /> <window_info id="Favorites" order="2" side_tool="true" />
@ -353,7 +467,7 @@
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" /> <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="SciView" order="3" /> <window_info anchor="right" id="SciView" order="3" />
<window_info anchor="right" id="Database" order="4" /> <window_info anchor="right" id="Database" order="4" />
</layout> </layout-to-restore>
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" /> <option name="version" value="1" />
@ -362,13 +476,7 @@
<option name="myLimit" value="2678400000" /> <option name="myLimit" value="2678400000" />
</component> </component>
<component name="editorHistoryManager"> <component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.less"> <entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.less" />
<provider selected="true" editor-type-id="text-editor">
<state>
<caret column="18" lean-forward="true" selection-start-column="18" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js"> <entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="85"> <state relative-caret-position="85">
@ -379,13 +487,7 @@
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/_style.css"> <entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/_style.css" />
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="272">
<caret line="16" lean-forward="true" selection-start-line="16" selection-end-line="17" selection-end-column="26" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss"> <entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="170"> <state relative-caret-position="170">
@ -393,68 +495,169 @@
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_master_detail.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="51">
<caret line="3" lean-forward="true" selection-start-line="3" selection-end-line="3" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/youtube/__init__.py" />
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html"> <entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="340">
<caret line="20" column="29" selection-start-line="20" selection-start-column="29" selection-end-line="20" selection-end-column="29" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/../YoutubeApi-tests/get_playlist_info.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="340">
<caret line="47" column="22" lean-forward="true" selection-start-line="47" selection-start-column="22" selection-end-line="47" selection-end-column="22" />
<folding>
<element signature="e#0#43#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="138">
<caret line="22" column="45" selection-start-line="22" selection-start-column="45" selection-end-line="22" selection-end-column="45" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221"> <state relative-caret-position="221">
<caret line="13" column="72" selection-start-line="13" selection-start-column="72" selection-end-line="13" selection-end-column="72" /> <caret line="13" column="33" selection-start-line="13" selection-start-column="33" selection-end-line="13" selection-end-column="33" />
</state>
</provider>
</entry>
<entry file="das://2dac2136-d902-4d27-8789-9371934602fd/schema/main/table/ytmanagerapp_subscriptionfolder">
<provider selected="true" editor-type-id="com.intellij.database.editor.DatabaseTableFileEditorProvider">
<state>
<filtering enabled="true" />
</state>
</provider>
</entry>
<entry file="das://2dac2136-d902-4d27-8789-9371934602fd/schema/main/table/ytmanagerapp_subscription">
<provider selected="true" editor-type-id="com.intellij.database.editor.DatabaseTableFileEditorProvider">
<state>
<filtering enabled="true" />
</state>
</provider>
</entry>
<entry file="das://2dac2136-d902-4d27-8789-9371934602fd/schema/main/table/ytmanagerapp_channel">
<provider selected="true" editor-type-id="com.intellij.database.editor.DatabaseTableFileEditorProvider">
<state>
<filtering enabled="true" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManager/wsgi.py">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/YtManager/settings.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-6">
<caret line="16" lean-forward="true" selection-start-line="16" selection-end-line="16" />
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html"> <entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357"> <state relative-caret-position="377">
<caret line="26" column="4" lean-forward="true" selection-start-line="26" selection-start-column="4" selection-end-line="26" selection-end-column="4" /> <caret line="24" selection-start-line="24" selection-end-line="24" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/apps.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="153">
<caret line="9" lean-forward="true" selection-start-line="9" selection-end-line="9" />
<folding>
<marker date="1538943422238" expanded="true" signature="120:125" ph="..." />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/admin.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="6" selection-start-line="6" selection-end-line="6" />
<folding>
<element signature="e#0#32#0" expanded="true" />
</folding>
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/views.py"> <entry file="file://$PROJECT_DIR$/YtManagerApp/views.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="334"> <state relative-caret-position="469">
<caret line="62" lean-forward="true" selection-start-line="62" selection-end-line="62" /> <caret line="87" lean-forward="true" selection-start-line="87" selection-end-line="87" />
<folding> <folding>
<element signature="e#0#35#0" expanded="true" /> <element signature="e#0#35#0" expanded="true" />
</folding> </folding>
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/management.py"> <entry file="file://$PROJECT_DIR$/YtManager/urls.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504"> <state relative-caret-position="289">
<caret line="42" column="11" selection-start-line="42" selection-start-column="11" selection-end-line="42" selection-end-column="11" /> <caret line="17" selection-start-line="17" selection-end-line="17" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/__init__.py">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="438">
<caret line="338" column="3" selection-start-line="338" selection-start-column="3" selection-end-line="338" selection-end-column="3" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="439">
<caret line="70" lean-forward="true" selection-start-line="70" selection-end-line="70" />
</state>
</provider>
</entry>
<entry file="das://2dac2136-d902-4d27-8789-9371934602fd/schema/main/table/ytmanagerapp_video">
<provider selected="true" editor-type-id="com.intellij.database.editor.DatabaseTableFileEditorProvider">
<state>
<filtering enabled="true" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/youtube.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="354">
<caret line="206" column="24" selection-start-line="206" selection-start-column="24" selection-end-line="206" selection-end-column="24" />
<folding> <folding>
<marker date="1538766618853" expanded="true" signature="90:91" ph="..." /> <element signature="e#0#43#0" expanded="true" />
<marker date="1538766618853" expanded="true" signature="642:651" ph="..." /> <marker date="1538948346076" expanded="true" signature="2071:2953" ph="..." />
<marker date="1538948346076" expanded="true" signature="5760:6122" ph="..." />
<marker date="1538948346076" expanded="true" signature="5843:6122" ph="..." />
</folding> </folding>
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/YtManager/urls.py"> <entry file="file://$PROJECT_DIR$/YtManagerApp/management.py">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="272"> <state relative-caret-position="208">
<caret line="19" lean-forward="true" selection-start-line="19" selection-end-line="19" /> <caret line="183" column="47" selection-start-line="183" selection-start-column="47" selection-end-line="183" selection-end-column="47" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="113">
<caret line="20" column="28" selection-start-line="20" selection-start-column="28" selection-end-line="20" selection-end-column="28" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1700">
<caret line="100" column="186" selection-start-line="100" selection-start-column="186" selection-end-line="100" selection-end-column="186" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357">
<caret line="21" column="30" lean-forward="true" selection-start-line="21" selection-start-column="30" selection-end-line="21" selection-end-column="30" />
<folding> <folding>
<element signature="e#433#440#1#HTML" expanded="true" /> <element signature="e#0#68#0" expanded="true" />
<marker date="1538954367974" expanded="true" signature="819:828" ph="..." />
<marker date="1538954367974" expanded="true" signature="1597:1603" ph="..." />
<marker date="1538954367974" expanded="true" signature="4940:6762" ph="..." />
<marker date="1538954367974" expanded="true" signature="5447:5983" ph="..." />
<marker date="1538954367974" expanded="true" signature="6067:6076" ph="..." />
<marker date="1538954367974" expanded="true" signature="6067:6496" ph="..." />
</folding> </folding>
</state> </state>
</provider> </provider>

View File

@ -22,6 +22,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '^zv8@i2h!ko2lo=%ivq(9e#x=%q*i^^)6#4@(juzdx%&0c+9a0' SECRET_KEY = '^zv8@i2h!ko2lo=%ivq(9e#x=%q*i^^)6#4@(juzdx%&0c+9a0'
YOUTUBE_API_KEY = "AIzaSyBabzE4Bup77WexdLMa9rN9z-wJidEfNX8"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@ -31,7 +33,7 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'YtManagerApp', 'YtManagerApp.apps.YtManagerAppConfig',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',

View File

@ -24,5 +24,7 @@ urlpatterns = [
path('ajax/get_folders', views.ajax_get_folders, name='ajax_get_folders'), path('ajax/get_folders', views.ajax_get_folders, name='ajax_get_folders'),
path('ajax/edit_folder', views.ajax_edit_folder, name='ajax_edit_folder'), path('ajax/edit_folder', views.ajax_edit_folder, name='ajax_edit_folder'),
path('ajax/delete_folder/<int:fid>/', views.ajax_delete_folder, name='ajax_delete_folder'), path('ajax/delete_folder/<int:fid>/', views.ajax_delete_folder, name='ajax_delete_folder'),
path('ajax/edit_subscription', views.ajax_edit_subscription, name='ajax_edit_subscription'),
path('ajax/delete_subscription/<int:sid>/', views.ajax_delete_subscription, name='ajax_delete_subscription'),
path(r'', views.index, name='home') path(r'', views.index, name='home')
] ]

View File

@ -1,5 +1,9 @@
from django.apps import AppConfig from django.apps import AppConfig
class YtmanagerappConfig(AppConfig): class YtManagerAppConfig(AppConfig):
name = 'YtManagerApp' name = 'YtManagerApp'
def ready(self):
from .management import SubscriptionManager
SubscriptionManager.start_scheduler()

View File

@ -1,5 +1,7 @@
from .models import SubscriptionFolder, Subscription, Video from .models import SubscriptionFolder, Subscription, Video, Channel
from .youtube import YoutubeAPI, YoutubeChannelInfo, YoutubePlaylistItem
from apscheduler.schedulers.background import BackgroundScheduler
import os
class FolderManager(object): class FolderManager(object):
@ -22,7 +24,7 @@ class FolderManager(object):
folder.save() folder.save()
@staticmethod @staticmethod
def __validate(folder): def __validate(folder: SubscriptionFolder):
# Make sure folder name is unique in the parent folder # Make sure folder name is unique in the parent folder
for dbFolder in SubscriptionFolder.objects.filter(parent_id=folder.parent_id): for dbFolder in SubscriptionFolder.objects.filter(parent_id=folder.parent_id):
if dbFolder.id != folder.id and dbFolder.name == folder.name: if dbFolder.id != folder.id and dbFolder.name == folder.name:
@ -42,3 +44,141 @@ class FolderManager(object):
def delete(fid: int): def delete(fid: int):
folder = SubscriptionFolder.objects.get(id=fid) folder = SubscriptionFolder.objects.get(id=fid)
folder.delete() folder.delete()
class SubscriptionManager(object):
__scheduler = BackgroundScheduler()
@staticmethod
def create_or_edit(sid, url, name, parent_id):
# Create or edit
if sid == '#':
sub = Subscription()
SubscriptionManager.create(url, parent_id, YoutubeAPI.build_public())
else:
sub = Subscription.objects.get(id=int(sid))
sub.name = name
if parent_id == '#':
sub.parent_folder = None
else:
sub.parent_folder = SubscriptionFolder.objects.get(id=int(parent_id))
sub.save()
@staticmethod
def create(url, parent_id, yt_api: YoutubeAPI):
sub = Subscription()
# Set parent
if parent_id == '#':
sub.parent_folder = None
else:
sub.parent_folder = SubscriptionFolder.objects.get(id=int(parent_id))
# Pull information about the channel and playlist
url_type, url_id = yt_api.parse_channel_url(url)
if url_type == 'playlist_id':
info_playlist = yt_api.get_playlist_info(url_id)
channel = SubscriptionManager.__get_or_create_channel('channel_id', info_playlist.getChannelId(), yt_api)
sub.name = info_playlist.getTitle()
sub.playlist_id = info_playlist.getId()
sub.description = info_playlist.getDescription()
sub.channel = channel
sub.icon_default = info_playlist.getDefaultThumbnailUrl()
sub.icon_best = info_playlist.getBestThumbnailUrl()
else:
channel = SubscriptionManager.__get_or_create_channel(url_type, url_id, yt_api)
# No point in getting the 'uploads' playlist info
sub.name = channel.name
sub.playlist_id = channel.upload_playlist_id
sub.description = channel.description
sub.channel = channel
sub.icon_default = channel.icon_default
sub.icon_best = channel.icon_best
sub.save()
@staticmethod
def __get_or_create_channel(url_type, url_id, yt_api: YoutubeAPI):
channel: Channel = None
info_channel: YoutubeChannelInfo = None
if url_type == 'user':
channel = Channel.find_by_username(url_id)
if not channel:
info_channel = yt_api.get_channel_info_by_username(url_id)
channel = Channel.find_by_channel_id(info_channel.getId())
elif url_type == 'channel_id':
channel = Channel.find_by_channel_id(url_id)
if not channel:
info_channel = yt_api.get_channel_info(url_id)
elif url_type == 'channel_custom':
channel = Channel.find_by_custom_url(url_id)
if not channel:
found_channel_id = yt_api.search_channel(url_id)
channel = Channel.find_by_channel_id(found_channel_id)
if not channel:
info_channel = yt_api.get_channel_info(found_channel_id)
# Store information about the channel
if info_channel:
if not channel:
channel = Channel()
if url_type == 'user':
channel.username = url_id
SubscriptionManager.__update_channel(channel, info_channel)
return channel
@staticmethod
def __update_channel(channel: Channel, yt_info: YoutubeChannelInfo):
channel.channel_id = yt_info.getId()
channel.custom_url = yt_info.getCustomUrl()
channel.name = yt_info.getTitle()
channel.description = yt_info.getDescription()
channel.icon_default = yt_info.getDefaultThumbnailUrl()
channel.icon_best = yt_info.getBestThumbnailUrl()
channel.upload_playlist_id = yt_info.getUploadsPlaylist()
channel.save()
@staticmethod
def __create_video(yt_video: YoutubePlaylistItem, subscription: Subscription):
video = Video()
video.video_id = yt_video.getVideoId()
video.name = yt_video.getTitle()
video.description = yt_video.getDescription()
video.watched = False
video.downloaded_path = None
video.subscription = subscription
video.playlist_index = yt_video.getPlaylistIndex()
video.publish_date = yt_video.getPublishDate()
video.icon_default = yt_video.getDefaultThumbnailUrl()
video.icon_best = yt_video.getBestThumbnailUrl()
video.save()
@staticmethod
def __synchronize(subscription: Subscription, yt_api: YoutubeAPI):
# Get list of videos
for video in yt_api.list_playlist_videos(subscription.playlist_id):
results = Video.objects.filter(video_id=video.getVideoId(), subscription=subscription)
if len(results) == 0:
print('New video for subscription "', subscription, '": ', video.getVideoId(), video.getTitle())
SubscriptionManager.__create_video(video, subscription)
@staticmethod
def __synchronize_all():
print("Running scheduled synchronization... ")
yt_api = YoutubeAPI.build_public()
for subscription in Subscription.objects.all():
SubscriptionManager.__synchronize(subscription, yt_api)
@staticmethod
def start_scheduler():
SubscriptionManager.__scheduler.add_job(SubscriptionManager.__synchronize_all, 'cron',
hour='*', minute=44, max_instances=1)
SubscriptionManager.__scheduler.start()

View File

@ -0,0 +1,57 @@
# Generated by Django 2.1.2 on 2018-10-07 17:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('YtManagerApp', '0004_auto_20181005_1626'),
]
operations = [
migrations.CreateModel(
name='Channel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('channel_id', models.TextField(unique=True)),
('username', models.TextField(null=True, unique=True)),
('custom_url', models.TextField(null=True, unique=True)),
('name', models.TextField()),
('description', models.TextField()),
('icon_default', models.TextField()),
('icon_best', models.TextField()),
('upload_playlist_id', models.TextField()),
],
),
migrations.RenameField(
model_name='subscription',
old_name='url',
new_name='playlist_id',
),
migrations.AddField(
model_name='subscription',
name='description',
field=models.TextField(default=None),
preserve_default=False,
),
migrations.AddField(
model_name='subscription',
name='icon_best',
field=models.TextField(default=None),
preserve_default=False,
),
migrations.AddField(
model_name='subscription',
name='icon_default',
field=models.TextField(default=None),
preserve_default=False,
),
migrations.AddField(
model_name='subscription',
name='channel',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='YtManagerApp.Channel'),
preserve_default=False,
),
]

View File

@ -0,0 +1,49 @@
# Generated by Django 2.1.2 on 2018-10-07 21:37
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('YtManagerApp', '0005_auto_20181007_2015'),
]
operations = [
migrations.RenameField(
model_name='video',
old_name='ytid',
new_name='video_id',
),
migrations.AddField(
model_name='video',
name='description',
field=models.TextField(default=None),
preserve_default=False,
),
migrations.AddField(
model_name='video',
name='icon_best',
field=models.TextField(default=None),
preserve_default=False,
),
migrations.AddField(
model_name='video',
name='icon_default',
field=models.TextField(default=None),
preserve_default=False,
),
migrations.AddField(
model_name='video',
name='playlist_index',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='video',
name='publish_date',
field=models.DateTimeField(default=django.utils.timezone.now),
preserve_default=False,
),
]

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
class SubscriptionFolder(models.Model): class SubscriptionFolder(models.Model):
name = models.TextField(null=False) name = models.TextField(null=False)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
@ -8,21 +9,65 @@ class SubscriptionFolder(models.Model):
return self.name return self.name
class Channel(models.Model):
channel_id = models.TextField(null=False, unique=True)
username = models.TextField(null=True, unique=True)
custom_url = models.TextField(null=True, unique=True)
name = models.TextField()
description = models.TextField()
icon_default = models.TextField()
icon_best = models.TextField()
upload_playlist_id = models.TextField()
@staticmethod
def find_by_channel_id(channel_id):
result = Channel.objects.filter(channel_id=channel_id)
if len(result) > 0:
return result.first()
return None
@staticmethod
def find_by_username(username):
result = Channel.objects.filter(username=username)
if len(result) > 0:
return result.first()
return None
@staticmethod
def find_by_custom_url(custom_url):
result = Channel.objects.filter(custom_url=custom_url)
if len(result) > 0:
return result.first()
return None
def __str__(self):
return self.name
class Subscription(models.Model): class Subscription(models.Model):
name = models.TextField(null=False) name = models.TextField(null=False)
parent_folder = models.ForeignKey(SubscriptionFolder, on_delete=models.SET_NULL, null=True, blank=True) parent_folder = models.ForeignKey(SubscriptionFolder, on_delete=models.SET_NULL, null=True, blank=True)
url = models.TextField(null=False, unique=True) playlist_id = models.TextField(null=False, unique=True)
description = models.TextField()
channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
icon_default = models.TextField()
icon_best = models.TextField()
def __str__(self): def __str__(self):
return self.name return self.name
class Video(models.Model): class Video(models.Model):
video_id = models.TextField(null=False)
name = models.TextField(null=False) name = models.TextField(null=False)
ytid = models.TextField(null=False) description = models.TextField()
downloaded_path = models.TextField(null=True, blank=True)
watched = models.BooleanField(default=False, null=False) watched = models.BooleanField(default=False, null=False)
downloaded_path = models.TextField(null=True, blank=True)
subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE) subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE)
playlist_index = models.IntegerField(null=False)
publish_date = models.DateTimeField(null=False)
icon_default = models.TextField()
icon_best = models.TextField()
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -1,36 +1,37 @@
<div id="folder_edit_dialog" class="modal" tabindex="-1" role="dialog"> <div id="folderEditDialog" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 id="folder_edit_dialog_title" class="modal-title">Edit folder</h5> <h5 id="folderEditDialog_Title" class="modal-title">Edit folder</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div id="folder_edit_dialog_loading" class="modal-body"> <div id="folderEditDialog_Loading" class="modal-body">
<div class="loading-dual-ring"></div> <div class="loading-dual-ring"></div>
<div id="folder_edit_dialog_error"></div>
</div> </div>
<form id="folder_edit_dialog_form" action="{% url 'ajax_edit_folder' %}" method="post"> <div id="folderEditDialog_Error">
</div>
<form id="folderEditDialog_Form" action="{% url 'ajax_edit_folder' %}" method="post">
<div class="modal-body"> <div class="modal-body">
{% csrf_token %} {% csrf_token %}
<input type="hidden" id="folder_edit_dialog_id" name="id" value="#"> <input type="hidden" id="folderEditDialog_Id" name="id" value="#">
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-3" for="folder_edit_dialog_name">Name</label> <label class="col-sm-3" for="folderEditDialog_Name">Name</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" class="form-control" id="folder_edit_dialog_name" name="name" placeholder="Folder name"> <input type="text" class="form-control" id="folderEditDialog_Name" name="name" placeholder="Folder name">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-3" for="folder_edit_dialog_parent">Parent folder</label> <label class="col-sm-3" for="folderEditDialog_Parent">Parent folder</label>
<div class="col-sm-9"> <div class="col-sm-9">
<select class="form-control" id="folder_edit_dialog_parent" name="parent"> <select class="form-control" id="folderEditDialog_Parent" name="parent">
</select> </select>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button id="folder_edit_dialog_submit" type="submit" class="btn btn-primary">Submit</button> <button id="folderEditDialog_Submit" type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div> </div>
</form> </form>

View File

@ -1,36 +1,42 @@
<div id="subscription_edit_dialog" class="modal" tabindex="-1" role="dialog"> <div id="subscriptionEditDialog" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 id="subscription_edit_dialog_title" class="modal-title">Edit subscription</h5> <h5 id="subscriptionEditDialog_Title" class="modal-title">Edit subscription</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div id="subscription_edit_dialog_loading" class="modal-body"> <div id="subscriptionEditDialog_Loading" class="modal-body">
<div class="loading-dual-ring"></div> <div class="loading-dual-ring"></div>
<div id="subscription_edit_dialog_error"></div>
</div> </div>
<form id="subscription_edit_dialog_form" action="{% url 'ajax_edit_subscription' %}" method="post"> <div id="subscriptionEditDialog_Error"></div>
<form id="subscriptionEditDialog_Form" action="{% url 'ajax_edit_subscription' %}" method="post">
<div class="modal-body"> <div class="modal-body">
{% csrf_token %} {% csrf_token %}
<input type="hidden" id="subscription_edit_dialog_id" name="id" value="#"> <input type="hidden" id="subscriptionEditDialog_Id" name="id" value="#">
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-3" for="subscription_edit_dialog_url">Link:</label> <label class="col-sm-3" for="subscriptionEditDialog_Url">Link:</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" class="form-control" id="subscription_edit_dialog_name" name="name" placeholder="subscription name"> <input type="text" class="form-control" id="subscriptionEditDialog_Url" name="url" placeholder="Subscription URL (playlist, channel)">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-3" for="subscription_edit_dialog_parent">Parent subscription</label> <label class="col-sm-3" for="subscriptionEditDialog_Name">Name:</label>
<div class="col-sm-9"> <div class="col-sm-9">
<select class="form-control" id="subscription_edit_dialog_parent" name="parent"> <input type="text" class="form-control" id="subscriptionEditDialog_Name" name="name" placeholder="Subscription name">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3" for="subscriptionEditDialog_Parent">Parent subscription</label>
<div class="col-sm-9">
<select class="form-control" id="subscriptionEditDialog_Parent" name="parent">
</select> </select>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button id="subscription_edit_dialog_submit" type="submit" class="btn btn-primary">Submit</button> <button id="subscriptionEditDialog_Submit" type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div> </div>
</form> </form>

View File

@ -12,6 +12,7 @@
</script> </script>
{% include 'YtManagerApp/controls/folder_edit_dialog.html' %} {% include 'YtManagerApp/controls/folder_edit_dialog.html' %}
{% include 'YtManagerApp/controls/subscription_edit_dialog.html' %}
{% endblock %} {% endblock %}
@ -19,7 +20,7 @@
<div class="btn-toolbar" role="toolbar" aria-label="Subscriptions toolbar"> <div class="btn-toolbar" role="toolbar" aria-label="Subscriptions toolbar">
<div class="btn-group btn-group-sm mr-2" role="group"> <div class="btn-group btn-group-sm mr-2" role="group">
<button type="button" class="btn btn-secondary" > <button id="btn_create_sub" type="button" class="btn btn-secondary" >
<i class="material-icons" aria-hidden="true">add</i> <i class="material-icons" aria-hidden="true">add</i>
</button> </button>
<button id="btn_create_folder" type="button" class="btn btn-secondary"> <button id="btn_create_folder" type="button" class="btn btn-secondary">

View File

@ -1,80 +1,227 @@
function folderEditDialog_Show(isNew, editNode) class Dialog {
{ constructor(modalId) {
let dialog = $("#folder_edit_dialog"); this.modal = $(modalId);
dialog.find('#folder_edit_dialog_title').text(isNew ? "New folder" : "Edit folder"); this.title = $(modalId + "_Title");
dialog.find("#folder_edit_dialog_loading").show(); this.form = $(modalId + "_Form");
dialog.find("#folder_edit_dialog_error").hide(); this.error = $(modalId + "_Error");
dialog.find("#folder_edit_dialog_form").hide(); this.loading = $(modalId + "_Loading");
dialog.modal(); this.btnSubmit = $(modalId + "_Submit");
this.setState('normal');
}
setTitle(value) {
this.title.text(value);
}
setState(state) {
if (state === 'loading') {
this.loading.show();
this.error.hide();
this.form.hide();
}
if (state === 'error') {
this.loading.hide();
this.error.show();
this.form.hide();
}
if (state === 'normal') {
this.loading.hide();
this.error.hide();
this.form.show();
}
}
setError(text) {
this.error.text(text);
}
showModal() {
this.modal.modal();
}
hideModal() {
this.modal.modal('hide');
}
}
class FolderEditDialog extends Dialog {
constructor (modalId) {
super(modalId);
this.field_Id = $(modalId + "_Id");
this.field_Name = $(modalId + "_Name");
this.field_Parent = $(modalId + "_Parent");
let pThis = this;
this.form.submit(function(e) {
pThis.submit(e);
})
}
setParentFolderOptions(folders, selectedId)
{
// Populate list of folders
this.field_Parent.empty();
this.field_Parent.append(new Option('(None)', '#'));
for (let folder of folders)
{
let o = new Option(folder.text, folder.id);
if (selectedId != null && folder.id.toString() === selectedId.toString())
o.selected = true;
this.field_Parent.append(o);
}
}
show (isNew, editNode) {
let pThis = this;
this.setTitle(isNew ? "New folder" : "Edit folder");
this.setState('loading');
this.showModal();
$.get("{% url 'ajax_get_folders' %}") $.get("{% url 'ajax_get_folders' %}")
.done(function(folders) .done(function(folders)
{ {
// Populate list of folders
let selParent = dialog.find("#folder_edit_dialog_parent");
selParent.empty();
selParent.append(new Option('(None)', '#'));
let parentId = null; let parentId = null;
if (!isNew) { if (!isNew) {
parentId = editNode.parent.replace('folder', ''); parentId = editNode.parent.replace('folder', '');
} }
for (let folder of folders) pThis.setParentFolderOptions(folders, parentId);
{ pThis.setState('normal');
let o = new Option(folder.text, folder.id); pThis.btnSubmit.text(isNew ? "Create" : "Save");
if (!isNew && folder.id.toString() === parentId.toString())
o.selected = true;
selParent.append(o);
}
// Show form
dialog.find("#folder_edit_dialog_loading").hide();
dialog.find("#folder_edit_dialog_form").show();
dialog.find("#folder_edit_dialog_submit").text(isNew ? "Create" : "Save");
if (isNew) if (isNew)
{ {
dialog.find("#folder_edit_dialog_id").val('#'); pThis.field_Id.val('#');
dialog.find("#folder_edit_dialog_name").val(''); pThis.field_Name.val('');
} }
if (!isNew) if (!isNew)
{ {
idTrimmed = editNode.id.replace('folder', ''); let idTrimmed = editNode.id.replace('folder', '');
dialog.find("#folder_edit_dialog_id").val(idTrimmed); pThis.field_Id.val(idTrimmed);
dialog.find("#folder_edit_dialog_name").val(editNode.text); pThis.field_Name.val(editNode.text);
} }
}) })
.fail(function() { .fail(function() {
let msgError = dialog.find("#folder_edit_dialog_error"); pThis.setState('error');
msgError.show(); pThis.setError('An error occurred!');
msgError.text("An error occurred!");
}); });
} }
function folderEditDialog_ShowNew() showNew() {
{ this.show(true, null);
folderEditDialog_Show(true, null); }
}
function folderEditDialog_Close() showEdit(editNode) {
{ this.show(false, editNode);
$("#folder_edit_dialog").modal('hide'); }
}
function folderEditDialog_Submit(e) submit(e) {
{ let url = this.form.attr('action');
let form = $(this);
let url = form.attr('action');
$.post(url, form.serialize()) $.post(url, this.form.serialize())
.done(tree_Refresh); .done(tree_Refresh);
folderEditDialog_Close(); this.hideModal();
e.preventDefault(); e.preventDefault();
}
} }
class SubscriptionEditDialog extends Dialog {
constructor (modalId) {
super(modalId);
this.field_Id = $(modalId + "_Id");
this.field_Url = $(modalId + "_Url");
this.field_Name = $(modalId + "_Name");
this.field_Parent = $(modalId + "_Parent");
let pThis = this;
this.form.submit(function(e) {
pThis.submit(e);
})
}
setParentFolderOptions(folders, selectedId)
{
// Populate list of folders
this.field_Parent.empty();
this.field_Parent.append(new Option('(None)', '#'));
for (let folder of folders)
{
let o = new Option(folder.text, folder.id);
if (selectedId != null && folder.id.toString() === selectedId.toString())
o.selected = true;
this.field_Parent.append(o);
}
}
show (isNew, editNode) {
let pThis = this;
this.setTitle(isNew ? "New subscription" : "Edit subscription");
this.setState('loading');
this.showModal();
$.get("{% url 'ajax_get_folders' %}")
.done(function(folders)
{
let parentId = null;
if (!isNew) {
parentId = editNode.parent.replace('folder', '');
}
pThis.setParentFolderOptions(folders, parentId);
pThis.setState('normal');
pThis.btnSubmit.text(isNew ? "Create" : "Save");
if (isNew)
{
pThis.field_Id.val('#');
pThis.field_Url.show();
pThis.field_Url.val('');
pThis.field_Name.hide();
pThis.field_Name.val('');
}
if (!isNew)
{
let idTrimmed = editNode.id.replace('sub', '');
pThis.field_Id.val(idTrimmed);
pThis.field_Url.hide();
pThis.field_Url.val('');
pThis.field_Name.show();
pThis.field_Name.val(editNode.text);
}
})
.fail(function() {
pThis.setState('error');
pThis.setError('An error occurred!');
});
}
showNew() {
this.show(true, null);
}
showEdit(editNode) {
this.show(false, editNode);
}
submit(e) {
let url = this.form.attr('action');
$.post(url, this.form.serialize())
.done(tree_Refresh);
this.hideModal();
e.preventDefault();
}
}
function treeNode_Edit() function treeNode_Edit()
{ {
let selectedNodes = $("#tree-wrapper").jstree('get_selected', true); let selectedNodes = $("#tree-wrapper").jstree('get_selected', true);
@ -82,10 +229,10 @@ function treeNode_Edit()
{ {
let node = selectedNodes[0]; let node = selectedNodes[0];
if (node.type === 'folder') { if (node.type === 'folder') {
folderEditDialog_Show(false, node); folderEditDialog.showEdit(node);
} }
else { else {
// TODO... subscriptionEditDialog.showEdit(node);
} }
} }
} }
@ -106,7 +253,13 @@ function treeNode_Delete()
} }
} }
else { else {
// TODO... let subId = node.id.toString().replace('sub', '');
if (confirm('Are you sure you want to delete subscription "' + node.text + '"?'))
{
$.post("{% url 'ajax_delete_subscription' 99999 %}".replace('99999', subId), {
csrfmiddlewaretoken: '{{ csrf_token }}'
}).done(tree_Refresh);
}
} }
} }
} }
@ -163,12 +316,24 @@ function tree_OnSelectionChanged(e, data)
node = data.instance.get_selected(true)[0]; node = data.instance.get_selected(true)[0];
} }
///
/// Globals
///
let folderEditDialog = null;
let subscriptionEditDialog = null;
///
/// Initialization
///
$(document).ready(function () $(document).ready(function ()
{ {
tree_Initialize(); tree_Initialize();
$("#btn_create_folder").on("click", folderEditDialog_ShowNew);
folderEditDialog = new FolderEditDialog('#folderEditDialog');
subscriptionEditDialog = new SubscriptionEditDialog('#subscriptionEditDialog');
$("#btn_create_sub").on("click", function () { subscriptionEditDialog.showNew(); });
$("#btn_create_folder").on("click", function () { folderEditDialog.showNew(); });
$("#btn_edit_node").on("click", treeNode_Edit); $("#btn_edit_node").on("click", treeNode_Edit);
$("#btn_delete_node").on("click", treeNode_Delete); $("#btn_delete_node").on("click", treeNode_Delete);
$("#folder_edit_dialog_form").submit(folderEditDialog_Submit);
}); });

View File

@ -1,7 +1,7 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponse, HttpRequest, JsonResponse from django.http import HttpResponse, HttpRequest, JsonResponse
from .models import SubscriptionFolder, Subscription from .models import SubscriptionFolder, Subscription
from .management import FolderManager from .management import FolderManager, SubscriptionManager
def get_children_recurse(parent_id): def get_children_recurse(parent_id):
@ -66,6 +66,22 @@ def ajax_delete_folder(request: HttpRequest, fid):
return HttpResponse() return HttpResponse()
def ajax_edit_subscription(request: HttpRequest):
if request.method == 'POST':
sid = request.POST['id']
name = request.POST['name']
url = request.POST['url']
parent_id = request.POST['parent']
SubscriptionManager.create_or_edit(sid, url, name, parent_id)
return HttpResponse()
def ajax_delete_subscription(request: HttpRequest, sid):
SubscriptionManager.delete(sid)
return HttpResponse()
def index(request: HttpRequest): def index(request: HttpRequest):
context = {} context = {}
return render(request, 'YtManagerApp/index.html', context) return render(request, 'YtManagerApp/index.html', context)

213
YtManagerApp/youtube.py Normal file
View File

@ -0,0 +1,213 @@
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from django.conf import settings
import re
API_SERVICE_NAME = 'youtube'
API_VERSION = 'v3'
class YoutubeChannelInfo(object):
def __init__(self, result_dict):
self.__id = result_dict['id']
self.__snippet = result_dict['snippet']
self.__contentDetails = result_dict['contentDetails']
def getId(self):
return self.__id
def getTitle(self):
return self.__snippet['title']
def getDescription(self):
return self.__snippet['description']
def getCustomUrl(self):
return self.__snippet['customUrl']
def getDefaultThumbnailUrl(self):
return self.__snippet['thumbnails']['default']['url']
def getBestThumbnailUrl(self):
best_url = None
best_res = 0
for _, thumb in self.__snippet['thumbnails'].items():
res = thumb['width'] * thumb['height']
if res > best_res:
best_res = res
best_url = thumb['url']
return best_url
def getUploadsPlaylist(self):
return self.__contentDetails['relatedPlaylists']['uploads']
class YoutubePlaylistInfo(object):
def __init__(self, result_dict):
self.__id = result_dict['id']
self.__snippet = result_dict['snippet']
def getId(self):
return self.__id
def getChannelId(self):
return self.__snippet['channelId']
def getTitle(self):
return self.__snippet['title']
def getDescription(self):
return self.__snippet['description']
def getDefaultThumbnailUrl(self):
return self.__snippet['thumbnails']['default']['url']
def getBestThumbnailUrl(self):
best_url = None
best_res = 0
for _, thumb in self.__snippet['thumbnails'].items():
res = thumb['width'] * thumb['height']
if res > best_res:
best_res = res
best_url = thumb['url']
return best_url
class YoutubePlaylistItem(object):
def __init__(self, result_dict):
self.__snippet = result_dict['snippet']
def getVideoId(self):
return self.__snippet['resourceId']['videoId']
def getPublishDate(self):
return self.__snippet['publishedAt']
def getTitle(self):
return self.__snippet['title']
def getDescription(self):
return self.__snippet['description']
def getDefaultThumbnailUrl(self):
return self.__snippet['thumbnails']['default']['url']
def getBestThumbnailUrl(self):
best_url = None
best_res = 0
for _, thumb in self.__snippet['thumbnails'].items():
res = thumb['width'] * thumb['height']
if res > best_res:
best_res = res
best_url = thumb['url']
return best_url
def getPlaylistIndex(self):
return self.__snippet['position']
class YoutubeAPI(object):
def __init__(self, service):
self.service = service
@staticmethod
def build_public() -> 'YoutubeAPI':
service = build(API_SERVICE_NAME, API_VERSION, developerKey=settings.YOUTUBE_API_KEY)
return YoutubeAPI(service)
@staticmethod
def parse_channel_url(url):
"""
Parses given channel url, returns a tuple of the form (type, value), where type can be one of:
* channel_id
* channel_custom
* user
* playlist_id
:param url: URL to parse
:return: (type, value) tuple
"""
match = re.search(r'youtube\.com/.*[&?]list=([^?&/]+)', url)
if match:
return 'playlist_id', match.group(1)
match = re.search(r'youtube\.com/user/([^?&/]+)', url)
if match:
return 'user', match.group(1)
match = re.search(r'youtube\.com/channel/([^?&/]+)', url)
if match:
return 'channel_id', match.group(1)
match = re.search(r'youtube\.com/(?:c/)?([^?&/]+)', url)
if match:
return 'channel_custom', match.group(1)
raise Exception('Unrecognized URL format!')
def get_playlist_info(self, list_id) -> YoutubePlaylistInfo:
result = self.service.playlists()\
.list(part='snippet', id=list_id)\
.execute()
if len(result['items']) <= 0:
raise Exception("Invalid playlist ID.")
return YoutubePlaylistInfo(result['items'][0])
def get_channel_info_by_username(self, user) -> YoutubeChannelInfo:
result = self.service.channels()\
.list(part='snippet,contentDetails', forUsername=user)\
.execute()
if len(result['items']) <= 0:
raise Exception('Invalid user.')
return YoutubeChannelInfo(result['items'][0])
def get_channel_info(self, channel_id) -> YoutubeChannelInfo:
result = self.service.channels()\
.list(part='snippet,contentDetails', id=channel_id)\
.execute()
if len(result['items']) <= 0:
raise Exception('Invalid channel ID.')
return YoutubeChannelInfo(result['items'][0])
def search_channel(self, custom) -> str:
result = self.service.search()\
.list(part='id', q=custom, type='channel')\
.execute()
if len(result['items']) <= 0:
raise Exception('Could not find channel!')
channel_result = result['items'][0]
return channel_result['id']['channelId']
def list_playlist_videos(self, playlist_id):
kwargs = {
"part": "snippet",
"maxResults": 50,
"playlistId": playlist_id
}
last_page = False
while not last_page:
result = self.service.playlistItems()\
.list(**kwargs)\
.execute()
for item in result['items']:
yield YoutubePlaylistItem(item)
if 'nextPageToken' in result:
kwargs['pageToken'] = result['nextPageToken']
else:
last_page = True
# @staticmethod
# def build_oauth() -> 'YoutubeAPI':
# flow =
# credentials =
# service = build(API_SERVICE_NAME, API_VERSION, credentials)