2019-06-03 18:37:28 +00:00
|
|
|
'''
|
|
|
|
Various utlity functions that are independant of any Django app or
|
|
|
|
model.
|
|
|
|
'''
|
|
|
|
|
|
|
|
from nturl2path import pathname2url as ntpathname2url
|
|
|
|
from nturl2path import url2pathname as url2ntpathname
|
2017-12-27 21:11:20 +00:00
|
|
|
import random
|
2018-01-05 20:18:12 +00:00
|
|
|
import re
|
2017-12-27 21:11:20 +00:00
|
|
|
import string
|
2018-01-05 20:18:12 +00:00
|
|
|
from unicodedata import normalize
|
2019-06-03 18:37:28 +00:00
|
|
|
from urllib.parse import urljoin, urlparse
|
|
|
|
from urllib.request import pathname2url, url2pathname
|
2017-12-27 21:11:20 +00:00
|
|
|
|
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
|
|
from django.db import connection
|
2019-06-03 18:37:28 +00:00
|
|
|
from django.utils.encoding import iri_to_uri, uri_to_iri
|
2017-12-27 21:11:20 +00:00
|
|
|
|
|
|
|
from .models import Setting
|
2019-06-10 11:37:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
GROUP_NT_UNC = r'file://[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+/'
|
|
|
|
|
|
|
|
GROUP_NT_DRIVE_LETTER = r'file:///[A-Za-z](?:\:|\|)/'
|
|
|
|
|
|
|
|
GROUP_NON_AUTH = r'file:///[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+'
|
|
|
|
|
|
|
|
FILE_IRI_PATTERN = (
|
|
|
|
r'^(?P<unc>' +
|
|
|
|
GROUP_NT_UNC +
|
|
|
|
r')|(?P<driveletter>' +
|
|
|
|
GROUP_NT_DRIVE_LETTER +
|
|
|
|
r')|(?P<nonauth>' +
|
|
|
|
GROUP_NON_AUTH +
|
|
|
|
r')'
|
|
|
|
)
|
2017-12-27 21:11:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def generate_password(length=32):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
|
|
|
Quick and dirty random password generator.
|
|
|
|
|
|
|
|
***WARNING*** - Although this is likely "good enough" to create a secure
|
|
|
|
password, there are no validations (suitible entropy, dictionary words,
|
|
|
|
etc.) and should not be completely trusted. YOU HAVE BEEN WARNED.
|
|
|
|
'''
|
2017-12-28 19:44:01 +00:00
|
|
|
chars = string.ascii_letters + string.digits + string.punctuation
|
2017-12-27 21:11:20 +00:00
|
|
|
rng = random.SystemRandom()
|
2017-12-28 19:44:01 +00:00
|
|
|
return ''.join([rng.choice(chars) for i in range(length)])
|
|
|
|
|
2017-12-27 21:11:20 +00:00
|
|
|
|
|
|
|
def get_len(rawqueryset):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2017-12-27 21:11:20 +00:00
|
|
|
Adds/Overrides a dynamic implementation of the length protocol to the
|
|
|
|
definition of RawQuerySet.
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2017-12-27 21:11:20 +00:00
|
|
|
def __len__(self):
|
|
|
|
params = ['{}'.format(p) for p in self.params]
|
2017-12-28 19:44:01 +00:00
|
|
|
sql = ''.join(('SELECT COUNT(*) FROM (',
|
|
|
|
rawqueryset.raw_query.format(tuple(params)),
|
|
|
|
') B;'))
|
2017-12-27 21:11:20 +00:00
|
|
|
cursor = connection.cursor()
|
|
|
|
cursor.execute(sql)
|
|
|
|
row = cursor.fetchone()
|
|
|
|
return row[0]
|
|
|
|
return __len__
|
|
|
|
|
2017-12-28 19:44:01 +00:00
|
|
|
|
2017-12-27 21:11:20 +00:00
|
|
|
def get_setting(name):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''Helper function to get dynamic settings from the database.'''
|
2017-12-27 21:11:20 +00:00
|
|
|
setting = Setting.objects.get(name=name)
|
|
|
|
return setting.get()
|
|
|
|
|
2017-12-28 19:44:01 +00:00
|
|
|
|
2017-12-27 21:11:20 +00:00
|
|
|
def set_setting(name, value, setting_type=None):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''Helper function to set dynamic settings from the database.'''
|
2017-12-27 21:11:20 +00:00
|
|
|
setting_types = {'Integer': 0, 'Float': 1, 'String': 2, 'Bool': 3}
|
|
|
|
try:
|
|
|
|
setting = Setting.objects.get(name=name)
|
|
|
|
setting.data = str(value)
|
|
|
|
if setting_type in setting_types:
|
|
|
|
setting.setting_type = setting_types[setting_type]
|
|
|
|
setting.save()
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
if setting_type in setting_types:
|
|
|
|
Setting.objects.create(name=name,
|
|
|
|
setting_type=setting_types[setting_type],
|
|
|
|
data=str(value))
|
|
|
|
else:
|
2017-12-28 19:44:01 +00:00
|
|
|
error_msg = 'New settings need type (Integer, Float, String, Bool)'
|
2017-12-27 21:11:20 +00:00
|
|
|
raise TypeError(error_msg)
|
|
|
|
return
|
2018-01-05 20:18:12 +00:00
|
|
|
|
|
|
|
|
2018-01-06 17:57:10 +00:00
|
|
|
def naturalize(text):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-01-05 20:18:12 +00:00
|
|
|
Return a normalized unicode string, with removed starting articles, for use
|
|
|
|
in natural sorting.
|
|
|
|
|
|
|
|
Code was inspired by 'django-naturalsortfield' from Nathan Reynolds:
|
|
|
|
https://github.com/nathforge/django-naturalsortfield
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-01-05 20:18:12 +00:00
|
|
|
def naturalize_int_match(match):
|
2018-01-06 18:11:51 +00:00
|
|
|
return '{:08d}'.format(int(match.group(0)))
|
2018-01-05 20:18:12 +00:00
|
|
|
|
2018-01-06 17:57:10 +00:00
|
|
|
text = normalize('NFKD', text).encode('ascii', 'ignore').decode('ascii')
|
|
|
|
text = text.lower()
|
|
|
|
punc = re.compile('[{}]'.format(re.escape(string.punctuation)))
|
|
|
|
text = re.sub(punc, ' ', text)
|
|
|
|
text = text.strip()
|
|
|
|
text = re.sub(r'^(a|an|the)\s+', '', text)
|
|
|
|
text = re.sub(r'\d+', naturalize_int_match, text)
|
2018-01-05 20:18:12 +00:00
|
|
|
|
2018-01-06 17:57:10 +00:00
|
|
|
return text
|
2018-01-14 20:21:04 +00:00
|
|
|
|
|
|
|
|
2018-01-14 23:38:14 +00:00
|
|
|
def quantify(quantity, model):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-01-14 23:38:14 +00:00
|
|
|
A message based on the quantity and singular/plural name of the model.
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-01-14 20:21:04 +00:00
|
|
|
if quantity == 1:
|
2018-01-14 23:38:14 +00:00
|
|
|
message = '1 {}'.format(model._meta.verbose_name)
|
2018-01-14 20:21:04 +00:00
|
|
|
else:
|
2018-01-14 23:38:14 +00:00
|
|
|
message = '{} {}'.format(str(quantity),
|
|
|
|
model._meta.verbose_name_plural)
|
2018-01-14 20:21:04 +00:00
|
|
|
return message
|
2018-01-14 23:38:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
def create_success_message(parent_model, parent_quantity, child_model,
|
|
|
|
child_quantity, remove=False):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-01-14 23:38:14 +00:00
|
|
|
Creates a message for displaying the success of model modification.
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-01-14 23:38:14 +00:00
|
|
|
p_message = quantify(parent_quantity, parent_model)
|
|
|
|
c_message = quantify(child_quantity, child_model)
|
|
|
|
if remove:
|
|
|
|
return '{} successfully removed from {}'.format(c_message, p_message)
|
2019-06-03 18:37:28 +00:00
|
|
|
return '{} successfully added to {}.'.format(c_message, p_message)
|
2018-03-26 19:28:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_pretty_time(seconds):
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-03-26 19:28:38 +00:00
|
|
|
Displays a human-readable representation of time.
|
2019-06-03 18:37:28 +00:00
|
|
|
'''
|
2018-03-26 19:28:38 +00:00
|
|
|
if seconds > 0:
|
|
|
|
periods = [
|
|
|
|
('year', 60*60*24*365.25),
|
|
|
|
('day', 60*60*24),
|
|
|
|
('hour', 60*60),
|
|
|
|
('minute', 60),
|
|
|
|
('second', 1)
|
|
|
|
]
|
|
|
|
strings = []
|
|
|
|
for period_name, period_seconds in periods:
|
|
|
|
if seconds >= period_seconds:
|
|
|
|
period_value, seconds = divmod(seconds, period_seconds)
|
|
|
|
strings.append('{} {}{}'.format(period_value,
|
|
|
|
period_name,
|
|
|
|
('s', '')[period_value == 1]))
|
|
|
|
return ', '.join(strings)
|
2019-06-03 18:37:28 +00:00
|
|
|
return 'Now'
|
|
|
|
|
|
|
|
|
|
|
|
def path_to_iri(path):
|
|
|
|
'''
|
|
|
|
OS-independant attempt at converting any OS absolute path to an
|
|
|
|
RFC3987-defined IRI along with the file scheme from RFC8089.
|
|
|
|
'''
|
|
|
|
# Looking to see if the path starts with a drive letter or UNC path
|
|
|
|
# (eg. 'D:\' or '\\')
|
|
|
|
windows = re.match(r'^(?:[A-Za-z]:|\\)\\', path)
|
|
|
|
if windows:
|
|
|
|
return uri_to_iri(urljoin('file:', ntpathname2url(path)))
|
|
|
|
return uri_to_iri(urljoin('file:', pathname2url(path)))
|
|
|
|
|
|
|
|
|
|
|
|
def iri_to_path(iri):
|
|
|
|
'''
|
|
|
|
OS-independant attempt at converting an RFC3987-defined IRI with a file
|
|
|
|
scheme from RFC8089 to an OS-specific absolute path.
|
|
|
|
'''
|
|
|
|
# Drive letter IRI will have three slashes followed by the drive letter
|
|
|
|
# UNC path IRI will have two slashes followed by the UNC path
|
|
|
|
uri = iri_to_uri(iri)
|
2019-06-06 19:45:25 +00:00
|
|
|
patt = r'^(?:' + GROUP_NT_DRIVE_LETTER + r'|' + GROUP_NT_UNC + r')'
|
2019-06-03 18:37:28 +00:00
|
|
|
windows = re.match(patt, uri)
|
|
|
|
if windows:
|
|
|
|
parse = urlparse(uri)
|
|
|
|
# UNC path URIs put the server name in the 'netloc' parameter.
|
|
|
|
if parse.netloc:
|
|
|
|
return '\\' + url2ntpathname('/' + parse.netloc + parse.path)
|
|
|
|
return url2ntpathname(parse.path)
|
|
|
|
return url2pathname(urlparse(uri).path)
|
2020-02-08 05:07:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def clean_quotes(unclean_string):
|
|
|
|
'''
|
|
|
|
Escapes quotes for use in the Liquidsoap parser.
|
|
|
|
'''
|
|
|
|
return unclean_string.replace('"', '\\"')
|
|
|
|
|
|
|
|
|
|
|
|
def beautify_artists(artists):
|
|
|
|
'''
|
|
|
|
Turns a list of one or more artists into a proper English listing.
|
|
|
|
'''
|
|
|
|
output = ', '
|
|
|
|
if len(artists) == 2:
|
|
|
|
output = ' & '
|
|
|
|
return clean_quotes(output.join(artists))
|