mirror of
https://github.com/FuzzyGrim/Yamtrack.git
synced 2026-03-03 02:57:01 +00:00
Show watch providers on media details page
This commit is contained in:
@@ -151,7 +151,7 @@ def movie(media_id):
|
|||||||
url = f"{base_url}/movie/{media_id}"
|
url = f"{base_url}/movie/{media_id}"
|
||||||
params = {
|
params = {
|
||||||
**base_params,
|
**base_params,
|
||||||
"append_to_response": "recommendations,external_ids,credits",
|
"append_to_response": "recommendations,external_ids,credits,watch/providers",
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -231,6 +231,7 @@ def movie(media_id):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
"external_links": get_external_links(response.get("external_ids", {})),
|
"external_links": get_external_links(response.get("external_ids", {})),
|
||||||
|
"providers": response.get("watch/providers", {}).get("results", {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.set(cache_key, data)
|
cache.set(cache_key, data)
|
||||||
@@ -274,14 +275,19 @@ def enrich_season_with_tv_data(season_data, tv_data, media_id, season_number):
|
|||||||
def fetch_and_cache_seasons(media_id, season_numbers, tv_data):
|
def fetch_and_cache_seasons(media_id, season_numbers, tv_data):
|
||||||
"""Fetch uncached seasons from API and cache them."""
|
"""Fetch uncached seasons from API and cache them."""
|
||||||
url = f"{base_url}/tv/{media_id}"
|
url = f"{base_url}/tv/{media_id}"
|
||||||
base_append = "recommendations,external_ids"
|
base_append = "recommendations,external_ids,watch/providers"
|
||||||
max_seasons_per_request = 18
|
max_seasons_per_request = 8
|
||||||
fetched_tv_data = tv_data
|
fetched_tv_data = tv_data
|
||||||
result_data = {}
|
result_data = {}
|
||||||
|
|
||||||
for i in range(0, len(season_numbers), max_seasons_per_request):
|
for i in range(0, len(season_numbers), max_seasons_per_request):
|
||||||
season_subset = season_numbers[i : i + max_seasons_per_request]
|
season_subset = season_numbers[i : i + max_seasons_per_request]
|
||||||
append_text = ",".join([f"season/{season}" for season in season_subset])
|
append_text = ",".join(
|
||||||
|
[
|
||||||
|
f"season/{season},season/{season}/watch/providers"
|
||||||
|
for season in season_subset
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
**base_params,
|
**base_params,
|
||||||
@@ -317,7 +323,9 @@ def fetch_and_cache_seasons(media_id, season_numbers, tv_data):
|
|||||||
not_found_error = type("Error", (), {"response": not_found_response})
|
not_found_error = type("Error", (), {"response": not_found_response})
|
||||||
raise services.ProviderAPIError(msg, error=not_found_error, details=msg)
|
raise services.ProviderAPIError(msg, error=not_found_error, details=msg)
|
||||||
|
|
||||||
season_data = process_season(response[season_key])
|
season_data = process_season(
|
||||||
|
response[season_key], response[f"{season_key}/watch/providers"]
|
||||||
|
)
|
||||||
season_data = enrich_season_with_tv_data(
|
season_data = enrich_season_with_tv_data(
|
||||||
season_data,
|
season_data,
|
||||||
fetched_tv_data,
|
fetched_tv_data,
|
||||||
@@ -371,7 +379,7 @@ def tv(media_id):
|
|||||||
url = f"{base_url}/tv/{media_id}"
|
url = f"{base_url}/tv/{media_id}"
|
||||||
params = {
|
params = {
|
||||||
**base_params,
|
**base_params,
|
||||||
"append_to_response": "recommendations,external_ids",
|
"append_to_response": "recommendations,external_ids,watch/providers",
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -434,10 +442,11 @@ def process_tv(response):
|
|||||||
"external_links": get_external_links(response.get("external_ids", {})),
|
"external_links": get_external_links(response.get("external_ids", {})),
|
||||||
"last_episode_season": last_episode["season_number"] if last_episode else None,
|
"last_episode_season": last_episode["season_number"] if last_episode else None,
|
||||||
"next_episode_season": next_episode["season_number"] if next_episode else None,
|
"next_episode_season": next_episode["season_number"] if next_episode else None,
|
||||||
|
"providers": response.get("watch/providers", {}).get("results", {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def process_season(response):
|
def process_season(response, providers_response=None):
|
||||||
"""Process the metadata for the selected season from The Movie Database."""
|
"""Process the metadata for the selected season from The Movie Database."""
|
||||||
episodes = response["episodes"]
|
episodes = response["episodes"]
|
||||||
num_episodes = len(episodes)
|
num_episodes = len(episodes)
|
||||||
@@ -475,6 +484,7 @@ def process_season(response):
|
|||||||
"total_runtime": total_runtime,
|
"total_runtime": total_runtime,
|
||||||
},
|
},
|
||||||
"episodes": response["episodes"],
|
"episodes": response["episodes"],
|
||||||
|
"providers": providers_response.get("results", {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -643,6 +653,31 @@ def get_collection(collection_response):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def filter_providers(all_providers, region):
|
||||||
|
"""Filter watch providers by region."""
|
||||||
|
if region == "":
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not all_providers:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Create a dict to get rid of duplicates across different provider types
|
||||||
|
region_providers = all_providers.get(region, {})
|
||||||
|
flatrate_providers = region_providers.get("flatrate", [])
|
||||||
|
free_providers = region_providers.get("free", [])
|
||||||
|
providers = {}
|
||||||
|
for provider in [*flatrate_providers, *free_providers]:
|
||||||
|
providers[provider.get("provider_id")] = provider
|
||||||
|
|
||||||
|
# Convert dict back to list and add image URLs
|
||||||
|
providers = list(providers.values())
|
||||||
|
for provider in providers:
|
||||||
|
provider["image"] = get_image_url(provider["logo_path"])
|
||||||
|
|
||||||
|
providers.sort(key=lambda e: e.get("display_priority", ""))
|
||||||
|
return providers
|
||||||
|
|
||||||
|
|
||||||
def process_episodes(season_metadata, episodes_in_db):
|
def process_episodes(season_metadata, episodes_in_db):
|
||||||
"""Process the episodes for the selected season."""
|
"""Process the episodes for the selected season."""
|
||||||
episodes_metadata = []
|
episodes_metadata = []
|
||||||
@@ -724,3 +759,37 @@ def episode(media_id, season_number, episode_number):
|
|||||||
error=not_found_error,
|
error=not_found_error,
|
||||||
details=msg,
|
details=msg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def watch_provider_regions():
|
||||||
|
"""Return the available watch provider regions from The Movie Database."""
|
||||||
|
cache_key = f"{Sources.TMDB.value}_watch_provider_regions"
|
||||||
|
data = cache.get(cache_key)
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
url = f"{base_url}/watch/providers/regions"
|
||||||
|
params = {**base_params}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = services.api_request(
|
||||||
|
Sources.TMDB.value,
|
||||||
|
"GET",
|
||||||
|
url,
|
||||||
|
params=params,
|
||||||
|
)
|
||||||
|
except requests.exceptions.HTTPError as error:
|
||||||
|
handle_error(error)
|
||||||
|
|
||||||
|
data = [("", "No Region")]
|
||||||
|
regions = response.get("results", [])
|
||||||
|
for region in sorted(regions, key=lambda r: r.get("english_name", "")):
|
||||||
|
key = region.get("iso_3166_1")
|
||||||
|
name = region.get("english_name")
|
||||||
|
if key:
|
||||||
|
if not name:
|
||||||
|
name = key
|
||||||
|
data.append((key, name))
|
||||||
|
|
||||||
|
cache.set(cache_key, data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|||||||
@@ -215,11 +215,19 @@ def media_details(request, source, media_type, media_id, title): # noqa: ARG001
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if media_type in ["tv", "movie"]:
|
||||||
|
watch_providers = tmdb.filter_providers(
|
||||||
|
media_metadata.get("providers"), request.user.watch_provider_region
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
watch_providers = None
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"media": media_metadata,
|
"media": media_metadata,
|
||||||
"media_type": media_type,
|
"media_type": media_type,
|
||||||
"user_medias": user_medias,
|
"user_medias": user_medias,
|
||||||
"current_instance": current_instance,
|
"current_instance": current_instance,
|
||||||
|
"watch_providers": watch_providers,
|
||||||
}
|
}
|
||||||
return render(request, "app/media_details.html", context)
|
return render(request, "app/media_details.html", context)
|
||||||
|
|
||||||
@@ -275,6 +283,9 @@ def season_details(request, source, media_id, title, season_number): # noqa: AR
|
|||||||
"media_type": MediaTypes.SEASON.value,
|
"media_type": MediaTypes.SEASON.value,
|
||||||
"user_medias": user_medias,
|
"user_medias": user_medias,
|
||||||
"current_instance": current_instance,
|
"current_instance": current_instance,
|
||||||
|
"watch_providers": tmdb.filter_providers(
|
||||||
|
season_metadata.get("providers"), request.user.watch_provider_region
|
||||||
|
),
|
||||||
}
|
}
|
||||||
return render(request, "app/media_details.html", context)
|
return render(request, "app/media_details.html", context)
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
@layer theme, base, components, utilities;
|
@layer theme, base, components, utilities;
|
||||||
@layer theme {
|
@layer theme {
|
||||||
:root, :host {
|
:root, :host {
|
||||||
--font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||||
'Noto Color Emoji';
|
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
monospace;
|
"Courier New", monospace;
|
||||||
--color-red-200: oklch(88.5% 0.062 18.334);
|
--color-red-200: oklch(88.5% 0.062 18.334);
|
||||||
--color-red-300: oklch(80.8% 0.114 19.571);
|
--color-red-300: oklch(80.8% 0.114 19.571);
|
||||||
--color-red-400: oklch(70.4% 0.191 22.216);
|
--color-red-400: oklch(70.4% 0.191 22.216);
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
tab-size: 4;
|
tab-size: 4;
|
||||||
font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji');
|
font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
|
||||||
font-feature-settings: var(--default-font-feature-settings, normal);
|
font-feature-settings: var(--default-font-feature-settings, normal);
|
||||||
font-variation-settings: var(--default-font-variation-settings, normal);
|
font-variation-settings: var(--default-font-variation-settings, normal);
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
code, kbd, samp, pre {
|
code, kbd, samp, pre {
|
||||||
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);
|
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
|
||||||
font-feature-settings: var(--default-mono-font-feature-settings, normal);
|
font-feature-settings: var(--default-mono-font-feature-settings, normal);
|
||||||
font-variation-settings: var(--default-mono-font-variation-settings, normal);
|
font-variation-settings: var(--default-mono-font-variation-settings, normal);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@@ -233,13 +233,13 @@
|
|||||||
:-moz-ui-invalid {
|
:-moz-ui-invalid {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button {
|
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
|
||||||
appearance: button;
|
appearance: button;
|
||||||
}
|
}
|
||||||
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
|
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
[hidden]:where(:not([hidden='until-found'])) {
|
[hidden]:where(:not([hidden="until-found"])) {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,6 +429,9 @@
|
|||||||
.my-3 {
|
.my-3 {
|
||||||
margin-block: calc(var(--spacing) * 3);
|
margin-block: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
|
.my-4 {
|
||||||
|
margin-block: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
.my-5 {
|
.my-5 {
|
||||||
margin-block: calc(var(--spacing) * 5);
|
margin-block: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
@@ -582,6 +585,10 @@
|
|||||||
.aspect-2\/3 {
|
.aspect-2\/3 {
|
||||||
aspect-ratio: 2/3;
|
aspect-ratio: 2/3;
|
||||||
}
|
}
|
||||||
|
.size-10 {
|
||||||
|
width: calc(var(--spacing) * 10);
|
||||||
|
height: calc(var(--spacing) * 10);
|
||||||
|
}
|
||||||
.h-3 {
|
.h-3 {
|
||||||
height: calc(var(--spacing) * 3);
|
height: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
@@ -906,6 +913,9 @@
|
|||||||
.flex-wrap {
|
.flex-wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.place-content-evenly {
|
||||||
|
place-content: space-evenly;
|
||||||
|
}
|
||||||
.items-baseline {
|
.items-baseline {
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/templates/app/icons/globe.svg
Normal file
7
src/templates/app/icons/globe.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="{{ classes }}">
|
||||||
|
<path d="M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 493 B |
@@ -435,6 +435,23 @@
|
|||||||
|
|
||||||
{# Media Details #}
|
{# Media Details #}
|
||||||
<h2 class="text-xl font-bold mb-4">Details</h2>
|
<h2 class="text-xl font-bold mb-4">Details</h2>
|
||||||
|
{% if watch_providers is not None %}
|
||||||
|
<div class="bg-[#2a2f35] p-4 rounded-lg my-4">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-400 mb-2">STREAMING</h3>
|
||||||
|
<div class="flex flex-wrap items-center gap-2 place-content-evenly">
|
||||||
|
{% if watch_providers %}
|
||||||
|
{% for provider in watch_providers %}
|
||||||
|
<img class="size-10 rounded-md"
|
||||||
|
src="{{ provider.image }}"
|
||||||
|
title="{{ provider.provider_name }}"
|
||||||
|
alt="{{ provider.provider_name }}" />
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p>No watch providers</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="bg-[#2a2f35] p-4 rounded-lg text-center md:text-start">
|
<div class="bg-[#2a2f35] p-4 rounded-lg text-center md:text-start">
|
||||||
{% if media.details.items %}
|
{% if media.details.items %}
|
||||||
{% for key, value in media.details.items %}
|
{% for key, value in media.details.items %}
|
||||||
|
|||||||
@@ -72,6 +72,23 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex items-center gap-2 text-lg font-medium text-gray-200">
|
||||||
|
{% include "app/icons/page.svg" with classes="w-5 h-5 text-indigo-400" %}
|
||||||
|
<h3>Attribution</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-300 pl-7">
|
||||||
|
Movie and TV streaming providers from
|
||||||
|
<a href="https://www.justwatch.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center gap-2 text-indigo-400 hover:text-indigo-300 transition-colors">
|
||||||
|
JustWatch
|
||||||
|
{% include "app/icons/external-link.svg" with classes="w-3 h-3 ml-1" %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 pt-4 border-t border-gray-700">
|
<div class="mt-8 pt-4 border-t border-gray-700">
|
||||||
<p class="text-gray-400 text-sm">
|
<p class="text-gray-400 text-sm">
|
||||||
Version: <span class="font-mono">{{ version }}</span>
|
Version: <span class="font-mono">{{ version }}</span>
|
||||||
|
|||||||
@@ -169,6 +169,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Watch Provider Region #}
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="flex items-center justify-between p-3 bg-[#39404b] rounded-md">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center text-gray-200 mb-1">
|
||||||
|
{% include "app/icons/globe.svg" with classes="w-5 h-5 mr-2" %}
|
||||||
|
<span class="text-sm font-medium">Watch provider region</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-400 ml-7">Choose the region to show watch providers for.</p>
|
||||||
|
</div>
|
||||||
|
<select name="watch_provider_region"
|
||||||
|
class="ml-4 p-2 bg-[#39404b] rounded-md text-white text-sm border border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-400">
|
||||||
|
{% for choice in watch_provider_choices %}
|
||||||
|
<option value="{{ choice.0 }}"
|
||||||
|
{% if user.watch_provider_region == choice.0 %}selected{% endif %}>{{ choice.1 }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-gray-600 my-5"></div>
|
<div class="border-t border-gray-600 my-5"></div>
|
||||||
|
|
||||||
{# Media Types Settings #}
|
{# Media Types Settings #}
|
||||||
|
|||||||
18
src/users/migrations/0050_user_watch_provider_region.py
Normal file
18
src/users/migrations/0050_user_watch_provider_region.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.11 on 2026-02-14 10:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0049_add_hide_zero_rating'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='watch_provider_region',
|
||||||
|
field=models.CharField(default='', help_text='Region to show watch providers for', max_length=5),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -330,6 +330,13 @@ class User(AbstractUser):
|
|||||||
help_text="Hide zero ratings from media cards",
|
help_text="Hide zero ratings from media cards",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Watch provider region
|
||||||
|
watch_provider_region = models.CharField(
|
||||||
|
max_length=5,
|
||||||
|
default="",
|
||||||
|
help_text="Region to show watch providers for",
|
||||||
|
)
|
||||||
|
|
||||||
# Calendar preferences
|
# Calendar preferences
|
||||||
calendar_layout = models.CharField(
|
calendar_layout = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from django.views.decorators.http import require_GET, require_http_methods, requ
|
|||||||
from django_celery_beat.models import PeriodicTask
|
from django_celery_beat.models import PeriodicTask
|
||||||
|
|
||||||
from app.models import Item, MediaTypes
|
from app.models import Item, MediaTypes
|
||||||
|
from app.providers import tmdb
|
||||||
from users.forms import NotificationSettingsForm, PasswordChangeForm, UserUpdateForm
|
from users.forms import NotificationSettingsForm, PasswordChangeForm, UserUpdateForm
|
||||||
from users.models import DateFormatChoices, QuickWatchDateChoices, TimeFormatChoices
|
from users.models import DateFormatChoices, QuickWatchDateChoices, TimeFormatChoices
|
||||||
|
|
||||||
@@ -223,6 +224,7 @@ def preferences(request):
|
|||||||
"quick_watch_date_choices": QuickWatchDateChoices.choices,
|
"quick_watch_date_choices": QuickWatchDateChoices.choices,
|
||||||
"date_format_choices": DateFormatChoices.choices,
|
"date_format_choices": DateFormatChoices.choices,
|
||||||
"time_format_choices": TimeFormatChoices.choices,
|
"time_format_choices": TimeFormatChoices.choices,
|
||||||
|
"watch_provider_choices": tmdb.watch_provider_regions(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -251,6 +253,7 @@ def preferences(request):
|
|||||||
TimeFormatChoices.HOUR_24,
|
TimeFormatChoices.HOUR_24,
|
||||||
)
|
)
|
||||||
media_types_checked = request.POST.getlist("media_types_checkboxes")
|
media_types_checked = request.POST.getlist("media_types_checkboxes")
|
||||||
|
request.user.watch_provider_region = request.POST.get("watch_provider_region", "")
|
||||||
|
|
||||||
# Update user preferences for each media type
|
# Update user preferences for each media type
|
||||||
for media_type in media_types:
|
for media_type in media_types:
|
||||||
|
|||||||
Reference in New Issue
Block a user