mirror of
https://github.com/meeb/tubesync.git
synced 2025-06-24 05:56:37 +00:00
Update legacy.py
This commit is contained in:
parent
4b42670824
commit
b5ee002404
@ -40,525 +40,6 @@ from ..choices import ( Val, CapChoices, Fallback, FileExtension,
|
|||||||
media_file_storage = FileSystemStorage(location=str(settings.DOWNLOAD_ROOT), base_url='/media-data/')
|
media_file_storage = FileSystemStorage(location=str(settings.DOWNLOAD_ROOT), base_url='/media-data/')
|
||||||
_srctype_dict = lambda n: dict(zip( YouTube_SourceType.values, (n,) * len(YouTube_SourceType.values) ))
|
_srctype_dict = lambda n: dict(zip( YouTube_SourceType.values, (n,) * len(YouTube_SourceType.values) ))
|
||||||
|
|
||||||
class Source(models.Model):
|
|
||||||
'''
|
|
||||||
A Source is a source of media. Currently, this is either a YouTube channel
|
|
||||||
or a YouTube playlist.
|
|
||||||
'''
|
|
||||||
|
|
||||||
sponsorblock_categories = CommaSepChoiceField(
|
|
||||||
_(''),
|
|
||||||
max_length=128,
|
|
||||||
possible_choices=SponsorBlock_Category.choices,
|
|
||||||
all_choice='all',
|
|
||||||
allow_all=True,
|
|
||||||
all_label='(All Categories)',
|
|
||||||
default='all',
|
|
||||||
help_text=_('Select the SponsorBlock categories that you wish to be removed from downloaded videos.')
|
|
||||||
)
|
|
||||||
embed_metadata = models.BooleanField(
|
|
||||||
_('embed metadata'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Embed metadata from source into file')
|
|
||||||
)
|
|
||||||
embed_thumbnail = models.BooleanField(
|
|
||||||
_('embed thumbnail'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Embed thumbnail into the file')
|
|
||||||
)
|
|
||||||
enable_sponsorblock = models.BooleanField(
|
|
||||||
_('enable sponsorblock'),
|
|
||||||
default=True,
|
|
||||||
help_text=_('Use SponsorBlock?')
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fontawesome icons used for the source on the front end
|
|
||||||
ICONS = _srctype_dict('<i class="fab fa-youtube"></i>')
|
|
||||||
|
|
||||||
# Format to use to display a URL for the source
|
|
||||||
URLS = dict(zip(
|
|
||||||
YouTube_SourceType.values,
|
|
||||||
(
|
|
||||||
'https://www.youtube.com/c/{key}',
|
|
||||||
'https://www.youtube.com/channel/{key}',
|
|
||||||
'https://www.youtube.com/playlist?list={key}',
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
# Format used to create indexable URLs
|
|
||||||
INDEX_URLS = dict(zip(
|
|
||||||
YouTube_SourceType.values,
|
|
||||||
(
|
|
||||||
'https://www.youtube.com/c/{key}/{type}',
|
|
||||||
'https://www.youtube.com/channel/{key}/{type}',
|
|
||||||
'https://www.youtube.com/playlist?list={key}',
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
# Callback functions to get a list of media from the source
|
|
||||||
INDEXERS = _srctype_dict(get_youtube_media_info)
|
|
||||||
|
|
||||||
# Field names to find the media ID used as the key when storing media
|
|
||||||
KEY_FIELD = _srctype_dict('id')
|
|
||||||
|
|
||||||
uuid = models.UUIDField(
|
|
||||||
_('uuid'),
|
|
||||||
primary_key=True,
|
|
||||||
editable=False,
|
|
||||||
default=uuid.uuid4,
|
|
||||||
help_text=_('UUID of the source')
|
|
||||||
)
|
|
||||||
created = models.DateTimeField(
|
|
||||||
_('created'),
|
|
||||||
auto_now_add=True,
|
|
||||||
db_index=True,
|
|
||||||
help_text=_('Date and time the source was created')
|
|
||||||
)
|
|
||||||
last_crawl = models.DateTimeField(
|
|
||||||
_('last crawl'),
|
|
||||||
db_index=True,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text=_('Date and time the source was last crawled')
|
|
||||||
)
|
|
||||||
source_type = models.CharField(
|
|
||||||
_('source type'),
|
|
||||||
max_length=1,
|
|
||||||
db_index=True,
|
|
||||||
choices=YouTube_SourceType.choices,
|
|
||||||
default=YouTube_SourceType.CHANNEL,
|
|
||||||
help_text=_('Source type')
|
|
||||||
)
|
|
||||||
key = models.CharField(
|
|
||||||
_('key'),
|
|
||||||
max_length=100,
|
|
||||||
db_index=True,
|
|
||||||
unique=True,
|
|
||||||
help_text=_('Source key, such as exact YouTube channel name or playlist ID')
|
|
||||||
)
|
|
||||||
name = models.CharField(
|
|
||||||
_('name'),
|
|
||||||
max_length=100,
|
|
||||||
db_index=True,
|
|
||||||
unique=True,
|
|
||||||
help_text=_('Friendly name for the source, used locally in TubeSync only')
|
|
||||||
)
|
|
||||||
directory = models.CharField(
|
|
||||||
_('directory'),
|
|
||||||
max_length=100,
|
|
||||||
db_index=True,
|
|
||||||
unique=True,
|
|
||||||
help_text=_('Directory name to save the media into')
|
|
||||||
)
|
|
||||||
media_format = models.CharField(
|
|
||||||
_('media format'),
|
|
||||||
max_length=200,
|
|
||||||
default=settings.MEDIA_FORMATSTR_DEFAULT,
|
|
||||||
help_text=_('File format to use for saving files, detailed options at bottom of page.')
|
|
||||||
)
|
|
||||||
index_schedule = models.IntegerField(
|
|
||||||
_('index schedule'),
|
|
||||||
choices=IndexSchedule.choices,
|
|
||||||
db_index=True,
|
|
||||||
default=IndexSchedule.EVERY_24_HOURS,
|
|
||||||
help_text=_('Schedule of how often to index the source for new media')
|
|
||||||
)
|
|
||||||
download_media = models.BooleanField(
|
|
||||||
_('download media'),
|
|
||||||
default=True,
|
|
||||||
help_text=_('Download media from this source, if not selected the source will only be indexed')
|
|
||||||
)
|
|
||||||
index_videos = models.BooleanField(
|
|
||||||
_('index videos'),
|
|
||||||
default=True,
|
|
||||||
help_text=_('Index video media from this source')
|
|
||||||
)
|
|
||||||
index_streams = models.BooleanField(
|
|
||||||
_('index streams'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Index live stream media from this source')
|
|
||||||
)
|
|
||||||
download_cap = models.IntegerField(
|
|
||||||
_('download cap'),
|
|
||||||
choices=CapChoices.choices,
|
|
||||||
default=CapChoices.CAP_NOCAP,
|
|
||||||
help_text=_('Do not download media older than this capped date')
|
|
||||||
)
|
|
||||||
delete_old_media = models.BooleanField(
|
|
||||||
_('delete old media'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Delete old media after "days to keep" days?')
|
|
||||||
)
|
|
||||||
days_to_keep = models.PositiveSmallIntegerField(
|
|
||||||
_('days to keep'),
|
|
||||||
default=14,
|
|
||||||
help_text=_('If "delete old media" is ticked, the number of days after which '
|
|
||||||
'to automatically delete media')
|
|
||||||
)
|
|
||||||
filter_text = models.CharField(
|
|
||||||
_('filter string'),
|
|
||||||
max_length=200,
|
|
||||||
default='',
|
|
||||||
blank=True,
|
|
||||||
help_text=_('Regex compatible filter string for video titles')
|
|
||||||
)
|
|
||||||
filter_text_invert = models.BooleanField(
|
|
||||||
_("invert filter text matching"),
|
|
||||||
default=False,
|
|
||||||
help_text="Invert filter string regex match, skip any matching titles when selected",
|
|
||||||
)
|
|
||||||
filter_seconds = models.PositiveIntegerField(
|
|
||||||
_('filter seconds'),
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
help_text=_('Filter Media based on Min/Max duration. Leave blank or 0 to disable filtering')
|
|
||||||
)
|
|
||||||
filter_seconds_min = models.BooleanField(
|
|
||||||
_('filter seconds min/max'),
|
|
||||||
choices=FilterSeconds.choices,
|
|
||||||
default=Val(FilterSeconds.MIN),
|
|
||||||
help_text=_('When Filter Seconds is > 0, do we skip on minimum (video shorter than limit) or maximum (video '
|
|
||||||
'greater than maximum) video duration')
|
|
||||||
)
|
|
||||||
delete_removed_media = models.BooleanField(
|
|
||||||
_('delete removed media'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Delete media that is no longer on this playlist')
|
|
||||||
)
|
|
||||||
delete_files_on_disk = models.BooleanField(
|
|
||||||
_('delete files on disk'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Delete files on disk when they are removed from TubeSync')
|
|
||||||
)
|
|
||||||
source_resolution = models.CharField(
|
|
||||||
_('source resolution'),
|
|
||||||
max_length=8,
|
|
||||||
db_index=True,
|
|
||||||
choices=SourceResolution.choices,
|
|
||||||
default=SourceResolution.VIDEO_1080P,
|
|
||||||
help_text=_('Source resolution, desired video resolution to download')
|
|
||||||
)
|
|
||||||
source_vcodec = models.CharField(
|
|
||||||
_('source video codec'),
|
|
||||||
max_length=8,
|
|
||||||
db_index=True,
|
|
||||||
choices=list(reversed(YouTube_VideoCodec.choices)),
|
|
||||||
default=YouTube_VideoCodec.VP9,
|
|
||||||
help_text=_('Source video codec, desired video encoding format to download (ignored if "resolution" is audio only)')
|
|
||||||
)
|
|
||||||
source_acodec = models.CharField(
|
|
||||||
_('source audio codec'),
|
|
||||||
max_length=8,
|
|
||||||
db_index=True,
|
|
||||||
choices=list(reversed(YouTube_AudioCodec.choices)),
|
|
||||||
default=YouTube_AudioCodec.OPUS,
|
|
||||||
help_text=_('Source audio codec, desired audio encoding format to download')
|
|
||||||
)
|
|
||||||
prefer_60fps = models.BooleanField(
|
|
||||||
_('prefer 60fps'),
|
|
||||||
default=True,
|
|
||||||
help_text=_('Where possible, prefer 60fps media for this source')
|
|
||||||
)
|
|
||||||
prefer_hdr = models.BooleanField(
|
|
||||||
_('prefer hdr'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Where possible, prefer HDR media for this source')
|
|
||||||
)
|
|
||||||
fallback = models.CharField(
|
|
||||||
_('fallback'),
|
|
||||||
max_length=1,
|
|
||||||
db_index=True,
|
|
||||||
choices=Fallback.choices,
|
|
||||||
default=Fallback.NEXT_BEST_HD,
|
|
||||||
help_text=_('What do do when media in your source resolution and codecs is not available')
|
|
||||||
)
|
|
||||||
copy_channel_images = models.BooleanField(
|
|
||||||
_('copy channel images'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Copy channel banner and avatar. These may be detected and used by some media servers')
|
|
||||||
)
|
|
||||||
copy_thumbnails = models.BooleanField(
|
|
||||||
_('copy thumbnails'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Copy thumbnails with the media, these may be detected and used by some media servers')
|
|
||||||
)
|
|
||||||
write_nfo = models.BooleanField(
|
|
||||||
_('write nfo'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Write an NFO file in XML with the media info, these may be detected and used by some media servers')
|
|
||||||
)
|
|
||||||
write_json = models.BooleanField(
|
|
||||||
_('write json'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Write a JSON file with the media info, these may be detected and used by some media servers')
|
|
||||||
)
|
|
||||||
has_failed = models.BooleanField(
|
|
||||||
_('has failed'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Source has failed to index media')
|
|
||||||
)
|
|
||||||
|
|
||||||
write_subtitles = models.BooleanField(
|
|
||||||
_('write subtitles'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Download video subtitles')
|
|
||||||
)
|
|
||||||
|
|
||||||
auto_subtitles = models.BooleanField(
|
|
||||||
_('accept auto-generated subs'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('Accept auto-generated subtitles')
|
|
||||||
)
|
|
||||||
sub_langs = models.CharField(
|
|
||||||
_('subs langs'),
|
|
||||||
max_length=30,
|
|
||||||
default='en',
|
|
||||||
help_text=_('List of subtitles langs to download, comma-separated. Example: en,fr or all,-fr,-live_chat'),
|
|
||||||
validators=[
|
|
||||||
RegexValidator(
|
|
||||||
regex=r"^(\-?[\_\.a-zA-Z-]+(,|$))+",
|
|
||||||
message=_('Subtitle langs must be a comma-separated list of langs. example: en,fr or all,-fr,-live_chat')
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Source')
|
|
||||||
verbose_name_plural = _('Sources')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self):
|
|
||||||
return self.ICONS.get(self.source_type)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def slugname(self):
|
|
||||||
replaced = self.name.replace('_', '-').replace('&', 'and').replace('+', 'and')
|
|
||||||
return slugify(replaced)[:80]
|
|
||||||
|
|
||||||
def deactivate(self):
|
|
||||||
self.download_media = False
|
|
||||||
self.index_streams = False
|
|
||||||
self.index_videos = False
|
|
||||||
self.index_schedule = IndexSchedule.NEVER
|
|
||||||
self.save(update_fields={
|
|
||||||
'download_media',
|
|
||||||
'index_streams',
|
|
||||||
'index_videos',
|
|
||||||
'index_schedule',
|
|
||||||
})
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_active(self):
|
|
||||||
active = (
|
|
||||||
self.download_media or
|
|
||||||
self.index_streams or
|
|
||||||
self.index_videos
|
|
||||||
)
|
|
||||||
return self.index_schedule and active
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_audio(self):
|
|
||||||
return self.source_resolution == SourceResolution.AUDIO.value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_playlist(self):
|
|
||||||
return self.source_type == YouTube_SourceType.PLAYLIST.value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_video(self):
|
|
||||||
return not self.is_audio
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_cap_date(self):
|
|
||||||
delta = self.download_cap
|
|
||||||
if delta > 0:
|
|
||||||
return timezone.now() - timedelta(seconds=delta)
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def days_to_keep_date(self):
|
|
||||||
delta = self.days_to_keep
|
|
||||||
if delta > 0:
|
|
||||||
return timezone.now() - timedelta(days=delta)
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extension(self):
|
|
||||||
'''
|
|
||||||
The extension is also used by youtube-dl to set the output container. As
|
|
||||||
it is possible to quite easily pick combinations of codecs and containers
|
|
||||||
which are invalid (e.g. OPUS audio in an MP4 container) just set this for
|
|
||||||
people. All video is set to mkv containers, audio-only is set to m4a or ogg
|
|
||||||
depending on audio codec.
|
|
||||||
'''
|
|
||||||
if self.is_audio:
|
|
||||||
if self.source_acodec == Val(YouTube_AudioCodec.MP4A):
|
|
||||||
return Val(FileExtension.M4A)
|
|
||||||
elif self.source_acodec == Val(YouTube_AudioCodec.OPUS):
|
|
||||||
return Val(FileExtension.OGG)
|
|
||||||
else:
|
|
||||||
raise ValueError('Unable to choose audio extension, uknown acodec')
|
|
||||||
else:
|
|
||||||
return Val(FileExtension.MKV)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_url(obj, source_type, key):
|
|
||||||
url = obj.URLS.get(source_type)
|
|
||||||
return url.format(key=key)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_index_url(obj, source_type, key, type):
|
|
||||||
url = obj.INDEX_URLS.get(source_type)
|
|
||||||
return url.format(key=key, type=type)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
return Source.create_url(self.source_type, self.key)
|
|
||||||
|
|
||||||
def get_index_url(self, type):
|
|
||||||
return Source.create_index_url(self.source_type, self.key, type)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def format_summary(self):
|
|
||||||
if self.is_audio:
|
|
||||||
vc = 'none'
|
|
||||||
else:
|
|
||||||
vc = self.source_vcodec
|
|
||||||
ac = self.source_acodec
|
|
||||||
f = ' 60FPS' if self.is_video and self.prefer_60fps else ''
|
|
||||||
h = ' HDR' if self.is_video and self.prefer_hdr else ''
|
|
||||||
return f'{self.source_resolution} (video:{vc}, audio:{ac}){f}{h}'.strip()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def directory_path(self):
|
|
||||||
download_dir = Path(media_file_storage.location)
|
|
||||||
return download_dir / self.type_directory_path
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type_directory_path(self):
|
|
||||||
if settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX:
|
|
||||||
if self.is_audio:
|
|
||||||
return Path(settings.DOWNLOAD_AUDIO_DIR) / self.directory
|
|
||||||
else:
|
|
||||||
return Path(settings.DOWNLOAD_VIDEO_DIR) / self.directory
|
|
||||||
else:
|
|
||||||
return Path(self.directory)
|
|
||||||
|
|
||||||
def make_directory(self):
|
|
||||||
return os.makedirs(self.directory_path, exist_ok=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def get_image_url(self):
|
|
||||||
if self.is_playlist:
|
|
||||||
raise SuspiciousOperation('This source is a playlist so it doesn\'t have thumbnail.')
|
|
||||||
|
|
||||||
return get_youtube_channel_image_info(self.url)
|
|
||||||
|
|
||||||
|
|
||||||
def directory_exists(self):
|
|
||||||
return (os.path.isdir(self.directory_path) and
|
|
||||||
os.access(self.directory_path, os.W_OK))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def key_field(self):
|
|
||||||
return self.KEY_FIELD.get(self.source_type, '')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def source_resolution_height(self):
|
|
||||||
return SourceResolutionInteger.get(self.source_resolution, 0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def can_fallback(self):
|
|
||||||
return self.fallback != Val(Fallback.FAIL)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def example_media_format_dict(self):
|
|
||||||
'''
|
|
||||||
Populates a dict with real-ish and some placeholder data for media name
|
|
||||||
format strings. Used for example filenames and media_format validation.
|
|
||||||
'''
|
|
||||||
fmt = []
|
|
||||||
if self.source_resolution:
|
|
||||||
fmt.append(self.source_resolution)
|
|
||||||
if self.source_vcodec:
|
|
||||||
fmt.append(self.source_vcodec.lower())
|
|
||||||
if self.source_acodec:
|
|
||||||
fmt.append(self.source_acodec.lower())
|
|
||||||
if self.prefer_60fps:
|
|
||||||
fmt.append('60fps')
|
|
||||||
if self.prefer_hdr:
|
|
||||||
fmt.append('hdr')
|
|
||||||
now = timezone.now()
|
|
||||||
return {
|
|
||||||
'yyyymmdd': now.strftime('%Y%m%d'),
|
|
||||||
'yyyy_mm_dd': now.strftime('%Y-%m-%d'),
|
|
||||||
'yyyy': now.strftime('%Y'),
|
|
||||||
'mm': now.strftime('%m'),
|
|
||||||
'dd': now.strftime('%d'),
|
|
||||||
'source': self.slugname,
|
|
||||||
'source_full': self.name,
|
|
||||||
'uploader': 'Some Channel Name',
|
|
||||||
'title': 'some-media-title-name',
|
|
||||||
'title_full': 'Some Media Title Name',
|
|
||||||
'key': 'SoMeUnIqUiD',
|
|
||||||
'format': '-'.join(fmt),
|
|
||||||
'playlist_title': 'Some Playlist Title',
|
|
||||||
'video_order': '01',
|
|
||||||
'ext': self.extension,
|
|
||||||
'resolution': self.source_resolution if self.source_resolution else '',
|
|
||||||
'height': '720' if self.source_resolution else '',
|
|
||||||
'width': '1280' if self.source_resolution else '',
|
|
||||||
'vcodec': self.source_vcodec.lower() if self.source_vcodec else '',
|
|
||||||
'acodec': self.source_acodec.lower(),
|
|
||||||
'fps': '24' if self.source_resolution else '',
|
|
||||||
'hdr': 'hdr' if self.source_resolution else ''
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_example_media_format(self):
|
|
||||||
try:
|
|
||||||
return self.media_format.format(**self.example_media_format_dict)
|
|
||||||
except Exception as e:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def is_regex_match(self, media_item_title):
|
|
||||||
if not self.filter_text:
|
|
||||||
return True
|
|
||||||
return bool(re.search(self.filter_text, media_item_title))
|
|
||||||
|
|
||||||
def get_index(self, type):
|
|
||||||
indexer = self.INDEXERS.get(self.source_type, None)
|
|
||||||
if not callable(indexer):
|
|
||||||
raise Exception(f'Source type f"{self.source_type}" has no indexer')
|
|
||||||
days = None
|
|
||||||
if self.download_cap_date:
|
|
||||||
days = timedelta(seconds=self.download_cap).days
|
|
||||||
response = indexer(self.get_index_url(type=type), days=days)
|
|
||||||
if not isinstance(response, dict):
|
|
||||||
return []
|
|
||||||
entries = response.get('entries', [])
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def index_media(self):
|
|
||||||
'''
|
|
||||||
Index the media source returning a list of media metadata as dicts.
|
|
||||||
'''
|
|
||||||
entries = list()
|
|
||||||
if self.index_videos:
|
|
||||||
entries += self.get_index('videos')
|
|
||||||
# Playlists do something different that I have yet to figure out
|
|
||||||
if not self.is_playlist:
|
|
||||||
if self.index_streams:
|
|
||||||
entries += self.get_index('streams')
|
|
||||||
|
|
||||||
if settings.MAX_ENTRIES_PROCESSING:
|
|
||||||
entries = entries[:settings.MAX_ENTRIES_PROCESSING]
|
|
||||||
return entries
|
|
||||||
|
|
||||||
def get_media_thumb_path(instance, filename):
|
def get_media_thumb_path(instance, filename):
|
||||||
# we don't want to use alternate names for thumb files
|
# we don't want to use alternate names for thumb files
|
||||||
if instance.thumb:
|
if instance.thumb:
|
||||||
|
Loading…
Reference in New Issue
Block a user