Repository: pymedusa/Medusa

Issue:
Black/White lists on Add New Show don't work
develop / e37859f7a3cadac6c72df8511fe754360f4e350b

Release groups black / white lists on the Add New Show page don't work (the buttons).
It used to not be visible due to a bug (I think), but now it is, and it needs to be fixed.

![2018-07-02_19-07-43](https://user-images.githubusercontent.com/10238474/42174491-392ae7f8-7e2b-11e8-9f32-191bf98b70a7.gif)




Discussion:
Needs to be replaces with the component 
@p0psicles Right. This is here so we don't forget about it.
@sharkykh any chance this could be fixed before release or shall we change milestones?
Probably not. `addShowOptions` needs to get converted to a Vue component first.
I've started but it's not going so smoothly.

Resolving patch:
diff --git a/medusa/common.py b/medusa/common.py
--- a/medusa/common.py
+++ b/medusa/common.py
@@ -260,6 +260,22 @@ def split_quality(quality):
 
         return sorted(allowed_qualities), sorted(preferred_qualities)
 
+    @staticmethod
+    def is_valid_combined_quality(quality):
+        """
+        Check quality value to make sure it is a valid combined quality.
+
+        :param quality: Quality to check
+        :type quality: int
+        :return: True if valid, False if not
+        """
+        for cur_qual in Quality.qualityStrings:
+            if cur_qual & quality:
+                quality -= cur_qual
+            if cur_qual << 16 & quality:
+                quality -= cur_qual << 16
+        return quality == 0
+
     @staticmethod
     def name_quality(name, anime=False, extend=True):
         """
diff --git a/medusa/server/api/v2/config.py b/medusa/server/api/v2/config.py
--- a/medusa/server/api/v2/config.py
+++ b/medusa/server/api/v2/config.py
@@ -17,6 +17,7 @@
     logger,
     ws,
 )
+from medusa.common import IGNORED, Quality, SKIPPED, WANTED
 from medusa.helper.mappings import NonEmptyDict
 from medusa.indexers.indexer_config import get_indexer_config
 from medusa.logger.adapters.style import BraceAdapter
@@ -51,6 +52,11 @@ def theme_name_setter(object, name, value):
     config.change_theme(value)
 
 
+def season_folders_validator(value):
+    """Validate default season folders setting."""
+    return not (app.NAMING_FORCE_FOLDERS and value is False)
+
+
 class ConfigHandler(BaseRequestHandler):
     """Config request handler."""
 
@@ -116,6 +122,15 @@ class ConfigHandler(BaseRequestHandler):
         'backlogOverview.period': StringField(app, 'BACKLOG_PERIOD'),
         'backlogOverview.status': StringField(app, 'BACKLOG_STATUS'),
         'rootDirs': ListField(app, 'ROOT_DIRS'),
+
+        'showDefaults.status': EnumField(app, 'STATUS_DEFAULT', (SKIPPED, WANTED, IGNORED), int),
+        'showDefaults.statusAfter': EnumField(app, 'STATUS_DEFAULT_AFTER', (SKIPPED, WANTED, IGNORED), int),
+        'showDefaults.quality': IntegerField(app, 'QUALITY_DEFAULT', validator=Quality.is_valid_combined_quality),
+        'showDefaults.subtitles': BooleanField(app, 'SUBTITLES_DEFAULT', validator=lambda v: app.USE_SUBTITLES, converter=bool),
+        'showDefaults.seasonFolders': BooleanField(app, 'SEASON_FOLDERS_DEFAULT', validator=season_folders_validator, converter=bool),
+        'showDefaults.anime': BooleanField(app, 'ANIME_DEFAULT', converter=bool),
+        'showDefaults.scene': BooleanField(app, 'SCENE_DEFAULT', converter=bool),
+
         'postProcessing.showDownloadDir': StringField(app, 'TV_DOWNLOAD_DIR'),
         'postProcessing.processAutomatically': BooleanField(app, 'PROCESS_AUTOMATICALLY'),
         'postProcessing.processMethod': StringField(app, 'PROCESS_METHOD'),
@@ -297,6 +312,15 @@ def data_main():
         section_data['subtitles']['enabled'] = bool(app.USE_SUBTITLES)
         section_data['recentShows'] = app.SHOWS_RECENT
 
+        section_data['showDefaults'] = {}
+        section_data['showDefaults']['status'] = app.STATUS_DEFAULT
+        section_data['showDefaults']['statusAfter'] = app.STATUS_DEFAULT_AFTER
+        section_data['showDefaults']['quality'] = app.QUALITY_DEFAULT
+        section_data['showDefaults']['subtitles'] = bool(app.SUBTITLES_DEFAULT)
+        section_data['showDefaults']['seasonFolders'] = bool(app.SEASON_FOLDERS_DEFAULT)
+        section_data['showDefaults']['anime'] = bool(app.ANIME_DEFAULT)
+        section_data['showDefaults']['scene'] = bool(app.SCENE_DEFAULT)
+
         section_data['news'] = NonEmptyDict()
         section_data['news']['lastRead'] = app.NEWS_LAST_READ
         section_data['news']['latest'] = app.NEWS_LATEST


Test patch:
diff --git a/tests/apiv2/test_config.py b/tests/apiv2/test_config.py
--- a/tests/apiv2/test_config.py
+++ b/tests/apiv2/test_config.py
@@ -78,6 +78,15 @@ def config_main(monkeypatch, app_config):
     config_data['subtitles']['enabled'] = bool(app.USE_SUBTITLES)
     config_data['recentShows'] = app.SHOWS_RECENT
 
+    config_data['showDefaults'] = {}
+    config_data['showDefaults']['status'] = app.STATUS_DEFAULT
+    config_data['showDefaults']['statusAfter'] = app.STATUS_DEFAULT_AFTER
+    config_data['showDefaults']['quality'] = app.QUALITY_DEFAULT
+    config_data['showDefaults']['subtitles'] = bool(app.SUBTITLES_DEFAULT)
+    config_data['showDefaults']['seasonFolders'] = bool(app.SEASON_FOLDERS_DEFAULT)
+    config_data['showDefaults']['anime'] = bool(app.ANIME_DEFAULT)
+    config_data['showDefaults']['scene'] = bool(app.SCENE_DEFAULT)
+
     config_data['news'] = NonEmptyDict()
     config_data['news']['lastRead'] = app.NEWS_LAST_READ
     config_data['news']['latest'] = app.NEWS_LATEST
diff --git a/tests/test_common.py b/tests/test_common.py
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -461,3 +461,33 @@ def test_wanted_quality():
 
     # Then
     assert actual is True
+
+
+@pytest.mark.parametrize('p', [
+    {  # p0 - Invalid combined quality
+        'quality': -4,
+        'expected': False
+    },
+    {  # p1 - Valid 'allowed' quality
+        'quality': Quality.HDTV,
+        'expected': True
+    },
+    {  # p2 - Valid 'allowed' quality + valid 'preferred' quality
+        'quality': Quality.combine_qualities([Quality.HDTV], [Quality.HDWEBDL]),
+        'expected': True
+    },
+    {  # p3 - Valid 'allowed' quality + **invalid** 'preferred' quality
+        'quality': Quality.combine_qualities([Quality.HDTV], [-4]),
+        'expected': False
+    },
+])
+def test_is_valid_combined_quality(p):
+    # Given
+    quality = p['quality']
+    expected = p['expected']
+
+    # When
+    actual = Quality.is_valid_combined_quality(quality)
+
+    # Then
+    assert expected == actual
diff --git a/themes-default/slim/test/__fixtures__/common.json b/themes-default/slim/test/__fixtures__/common.json
--- a/themes-default/slim/test/__fixtures__/common.json
+++ b/themes-default/slim/test/__fixtures__/common.json
@@ -192,6 +192,140 @@
             }
           }
         }
+      },
+      "namingForceFolders": false,
+      "showDefaults": {
+        "status": 5,
+        "statusAfter": 7,
+        "seasonFolders": false,
+        "scene": false,
+        "anime": false,
+        "quality": 328,
+        "subtitles": false
+      }
+    },
+    "statuses": {
+      "values": {
+        "ignored": 7,
+        "archived": 6,
+        "skipped": 5,
+        "snatchedBest": 12,
+        "wanted": 3,
+        "failed": 11,
+        "subtitled": 10,
+        "downloaded": 4,
+        "snatchedProper": 9,
+        "snatched": 2,
+        "unaired": 1,
+        "unset": -1
+      },
+      "strings": {
+        "1": "Unaired",
+        "2": "Snatched",
+        "3": "Wanted",
+        "4": "Downloaded",
+        "5": "Skipped",
+        "6": "Archived",
+        "7": "Ignored",
+        "9": "Snatched (Proper)",
+        "10": "Subtitled",
+        "11": "Failed",
+        "12": "Snatched (Best)",
+        "-1": "Unset"
+      }
+    },
+    "qualities": {
+      "values": {
+        "fullhdbluray": 512,
+        "rawhdtv": 16,
+        "uhd8kbluray": 32768,
+        "na": 0,
+        "hdwebdl": 64,
+        "uhd4ktv": 1024,
+        "sddvd": 4,
+        "uhd8kwebdl": 16384,
+        "uhd4kwebdl": 2048,
+        "hdtv": 8,
+        "uhd4kbluray": 4096,
+        "hdbluray": 256,
+        "unknown": 1,
+        "fullhdtv": 32,
+        "sdtv": 2,
+        "fullhdwebdl": 128,
+        "uhd8ktv": 8192
+      },
+      "anySets": {
+        "anywebdl": 192,
+        "anybluray": 768,
+        "anyhdtv": 40
+      },
+      "presets": {
+        "uhd": 64512,
+        "hd1080p": 672,
+        "uhd4k": 7168,
+        "hd720p": 328,
+        "uhd8k": 57344,
+        "any": 65518,
+        "hd": 1000,
+        "sd": 6
+      },
+      "strings": {
+        "cssClass": {
+          "0": "na",
+          "1": "Unknown",
+          "2": "SDTV",
+          "4": "SDDVD",
+          "8": "HD720p",
+          "16": "RawHD",
+          "32": "HD1080p",
+          "40": "any-hd",
+          "64": "HD720p",
+          "128": "HD1080p",
+          "192": "any-hd",
+          "256": "HD720p",
+          "512": "HD1080p",
+          "768": "any-hd",
+          "1024": "UHD-4K",
+          "2048": "UHD-4K",
+          "4096": "UHD-4K",
+          "8192": "UHD-8K",
+          "16384": "UHD-8K",
+          "32768": "UHD-8K"
+        },
+        "presets": {
+          "6": "SD",
+          "328": "HD720p",
+          "672": "HD1080p",
+          "1000": "HD",
+          "7168": "UHD-4K",
+          "57344": "UHD-8K",
+          "64512": "UHD",
+          "65518": "Any"
+        },
+        "values": {
+          "0": "N/A",
+          "1": "Unknown",
+          "2": "SDTV",
+          "4": "SD DVD",
+          "8": "720p HDTV",
+          "16": "RawHD",
+          "32": "1080p HDTV",
+          "64": "720p WEB-DL",
+          "128": "1080p WEB-DL",
+          "256": "720p BluRay",
+          "512": "1080p BluRay",
+          "1024": "4K UHD TV",
+          "2048": "4K UHD WEB-DL",
+          "4096": "4K UHD BluRay",
+          "8192": "8K UHD TV",
+          "16384": "8K UHD WEB-DL",
+          "32768": "8K UHD BluRay"
+        },
+        "anySets": {
+          "40": "HDTV",
+          "192": "WEB-DL",
+          "768": "BluRay"
+        }
       }
     },
     "metadata": {
diff --git a/themes-default/slim/test/specs/add-show-options.spec.js b/themes-default/slim/test/specs/add-show-options.spec.js
new file mode 100644
--- /dev/null
+++ b/themes-default/slim/test/specs/add-show-options.spec.js
@@ -0,0 +1,51 @@
+import test from 'ava';
+import Vuex from 'vuex';
+import VueRouter from 'vue-router';
+import { createLocalVue, mount } from '@vue/test-utils';
+import { AddShowOptions } from '../../src/components';
+import fixtures from '../__fixtures__/common';
+
+test.beforeEach(t => {
+    t.context.localVue = createLocalVue();
+    t.context.localVue.use(Vuex);
+    t.context.localVue.use(VueRouter);
+
+    const { state } = fixtures;
+    const { Store } = Vuex;
+    t.context.state = state;
+    t.context.store = new Store({ state });
+});
+
+test('renders with `enable-anime-options` disabled', t => {
+    const { localVue, store } = t.context;
+    const wrapper = mount(AddShowOptions, {
+        localVue,
+        store,
+        stubs: [
+            'quality-chooser',
+            'toggle-button'
+        ],
+        propsData: {
+            enableAnimeOptions: false
+        }
+    });
+
+    t.snapshot(wrapper.html());
+});
+
+test('renders with `enable-anime-options` enabled', t => {
+    const { localVue, store } = t.context;
+    const wrapper = mount(AddShowOptions, {
+        localVue,
+        store,
+        stubs: [
+            'quality-chooser',
+            'toggle-button'
+        ],
+        propsData: {
+            enableAnimeOptions: true
+        }
+    });
+
+    t.snapshot(wrapper.html());
+});
diff --git a/themes-default/slim/test/specs/snapshots/add-show-options.spec.js.md b/themes-default/slim/test/specs/snapshots/add-show-options.spec.js.md
new file mode 100644
--- /dev/null
+++ b/themes-default/slim/test/specs/snapshots/add-show-options.spec.js.md
@@ -0,0 +1,17 @@
+# Snapshot report for `test/specs/add-show-options.spec.js`
+
+The actual snapshot is saved in `add-show-options.spec.js.snap`.
+
+Generated by [AVA](https://ava.li).
+
+## renders with `enable-anime-options` disabled
+
+> Snapshot 1
+
+    '<div id="add-show-options-content"><fieldset class="component-group-list"><div class="form-group"><div class="row"><label for="customQuality" class="col-sm-2 control-label"><span>Quality</span></label><div class="col-sm-10 content"><quality-chooser-stub overall-quality="328"></quality-chooser-stub></div></div></div><div id="use-subtitles"><div id="config-toggle-slider-content"><div class="form-group"><div class="row"><label for="subtitles" class="col-sm-2 control-label"><span>Subtitles</span></label><div class="col-sm-10 content"><toggle-button-stub width="45" height="22" sync="" id="subtitles" name="subtitles"></toggle-button-stub><p>Download subtitles for this show?</p></div></div></div></div></div><div class="form-group"><div class="row"><label for="defaultStatus" class="col-sm-2 control-label"><span>Status for previously aired episodes</span></label><div class="col-sm-10 content"><select id="defaultStatus" class="form-control form-control-inline input-sm"><option value="5">Skipped</option><option value="3">Wanted</option><option value="7">Ignored</option></select></div></div></div><div class="form-group"><div class="row"><label for="defaultStatusAfter" class="col-sm-2 control-label"><span>Status for all future episodes</span></label><div class="col-sm-10 content"><select id="defaultStatusAfter" class="form-control form-control-inline input-sm"><option value="5">Skipped</option><option value="3">Wanted</option><option value="7">Ignored</option></select></div></div></div><div id="config-toggle-slider-content"><div class="form-group"><div class="row"><label for="season_folders" class="col-sm-2 control-label"><span>Season Folders</span></label><div class="col-sm-10 content"><toggle-button-stub width="45" height="22" sync="" id="season_folders" name="season_folders"></toggle-button-stub><p>Group episodes by season folders?</p></div></div></div></div><!----><!----><div id="config-toggle-slider-content"><div class="form-group"><div class="row"><label for="scene" class="col-sm-2 control-label"><span>Scene Numbering</span></label><div class="col-sm-10 content"><toggle-button-stub width="45" height="22" sync="" id="scene" name="scene"></toggle-button-stub><p>Is this show scene numbered?</p></div></div></div></div><div class="form-group"><div class="row"><label for="saveDefaultsButton" class="col-sm-2 control-label"><span>Use current values as the defaults</span></label><div class="col-sm-10 content"><button type="button" class="btn-medusa btn-inline">Save Defaults</button></div></div></div></fieldset></div>'
+
+## renders with `enable-anime-options` enabled
+
+> Snapshot 1
+
+    '<div id="add-show-options-content"><fieldset class="component-group-list"><div class="form-group"><div class="row"><label for="customQuality" class="col-sm-2 control-label"><span>Quality</span></label><div class="col-sm-10 content"><quality-chooser-stub overall-quality="328"></quality-chooser-stub></div></div></div><div id="use-subtitles"><div id="config-toggle-slider-content"><div class="form-group"><div class="row"><label for="subtitles" class="col-sm-2 control-label"><span>Subtitles</span></label><div class="col-sm-10 content"><toggle-button-stub width="45" height="22" sync="" id="subtitles" name="subtitles"></toggle-button-stub><p>Download subtitles for this show?</p></div></div></div></div></div><div class="form-group"><div class="row"><label for="defaultStatus" class="col-sm-2 control-label"><span>Status for previously aired episodes</span></label><div class="col-sm-10 content"><select id="defaultStatus" class="form-control form-control-inline input-sm"><option value="5">Skipped</option><option value="3">Wanted</option><option value="7">Ignored</option></select></div></div></div><div class="form-group"><div class="row"><label for="defaultStatusAfter" class="col-sm-2 control-label"><span>Status for all future episodes</span></label><div class="col-sm-10 content"><select id="defaultStatusAfter" class="form-control form-control-inline input-sm"><option value="5">Skipped</option><option value="3">Wanted</option><option value="7">Ignored</option></select></div></div></div><div id="config-toggle-slider-content"><div class="form-group"><div class="row"><label for="season_folders" class="col-sm-2 control-label"><span>Season Folders</span></label><div class="col-sm-10 content"><toggle-button-stub width="45" height="22" sync="" id="season_folders" name="season_folders"></toggle-button-stub><p>Group episodes by season folders?</p></div></div></div></div><div id="config-toggle-slider-content"><div class="form-group"><div class="row"><label for="anime" class="col-sm-2 control-label"><span>Anime</span></label><div class="col-sm-10 content"><toggle-button-stub width="45" height="22" sync="" id="anime" name="anime"></toggle-button-stub><p>Is this show an Anime?</p></div></div></div></div><!----><div id="config-toggle-slider-content"><div class="form-group"><div class="row"><label for="scene" class="col-sm-2 control-label"><span>Scene Numbering</span></label><div class="col-sm-10 content"><toggle-button-stub width="45" height="22" sync="" id="scene" name="scene"></toggle-button-stub><p>Is this show scene numbered?</p></div></div></div></div><div class="form-group"><div class="row"><label for="saveDefaultsButton" class="col-sm-2 control-label"><span>Use current values as the defaults</span></label><div class="col-sm-10 content"><button type="button" class="btn-medusa btn-inline">Save Defaults</button></div></div></div></fieldset></div>'
diff --git a/themes-default/slim/test/specs/snapshots/add-show-options.spec.js.snap b/themes-default/slim/test/specs/snapshots/add-show-options.spec.js.snap
new file mode 100644
Binary files /dev/null and b/themes-default/slim/test/specs/snapshots/add-show-options.spec.js.snap differ
diff --git a/themes-default/slim/test/specs/utils.spec.js b/themes-default/slim/test/specs/utils.spec.js
new file mode 100644
--- /dev/null
+++ b/themes-default/slim/test/specs/utils.spec.js
@@ -0,0 +1,15 @@
+import test from 'ava';
+import { combineQualities } from '../../src/utils';
+
+const testCases = [
+    /* C0 */ { allowed: [1, 2, 4], preferred: [], expected: 7 },
+    /* C1 */ { allowed: [1], preferred: [2, 4], expected: 393217 },
+    /* C2 */ { allowed: [32768], preferred: [32768], expected: 2147516416 }
+];
+testCases.forEach(({ allowed, preferred, expected }, caseIndex) => {
+    const testTitle = `[C${caseIndex}] combineQualities(${JSON.stringify(allowed)}, ${JSON.stringify(preferred)})`;
+    test(testTitle, t => {
+        const actual = combineQualities(allowed, preferred);
+        t.is(actual, expected);
+    });
+});