Files
tryton/modules/web_shortener/web.py
Cédric Krier 5310b00496 Remove grouped_slice per IN_MAX
The usage of ``grouped_slice`` without size is deprecated since `=
ANY(ARRAY[%s])` is used for PostgreSQL and SQLite is used only for tests.

The ``Database.IN_MAX`` is replaced by ``backend.MAX_QUERY_PARAMS`` which is a
more accurate limit of the backend.
The SQLite limits is indeed SQLITE_MAX_EXPR_DEPTH so we use a empirical value.
2026-03-22 21:05:26 +01:00

130 lines
4.1 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import logging
import string
from collections import defaultdict
from urllib.parse import quote, urljoin
from sql.aggregate import Count
import trytond.config as config
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.url import http_base
from trytond.wsgi import Base64Converter
ALPHABET = string.digits + string.ascii_lowercase
logger = logging.getLogger(__name__)
class ShortenedURL(ModelSQL, ModelView):
__name__ = 'web.shortened_url'
shortened_url = fields.Function(
fields.Char("Shortened URL"), 'get_url')
redirect_url = fields.Char("Redirect URL", readonly=True, required=True)
record = fields.Reference("Record", selection='get_models', readonly=True)
method = fields.Selection('get_methods', "Method", readonly=True)
count = fields.Function(fields.Integer("Click Count"), 'get_count')
@classmethod
def get_url(cls, shortened_urls, name):
urls = {}
url_parts = {
'database': Base64Converter(None).to_url(
Transaction().database.name),
}
url_base = config.get('web', 'shortener_base', default=http_base())
for shortened in shortened_urls:
url_parts['short_id'] = cls._shorten(shortened.id)
urls[shortened.id] = urljoin(
url_base, quote('/s/%(database)s$%(short_id)s' % url_parts))
return urls
@classmethod
def get_count(cls, shortened_urls, name):
pool = Pool()
URLAccess = pool.get('web.shortened_url.access')
access = URLAccess.__table__()
cursor = Transaction().connection.cursor()
counts = defaultdict(int)
cursor.execute(*access.select(
access.url, Count(access.id),
where=fields.SQL_OPERATORS['in'](
access.url, map(int, shortened_urls)),
group_by=[access.url]))
counts.update(cursor)
return counts
@classmethod
def _get_models(cls):
return []
@classmethod
def get_models(cls):
pool = Pool()
Model = pool.get('ir.model')
get_name = Model.get_name
models = cls._get_models()
return [(None, '')] + [(m, get_name(m)) for m in models]
@classmethod
def _get_methods(cls):
return []
@fields.depends('record')
def get_methods(self):
def func(option):
if not self.record:
return True
else:
name = option[0].split('|')[0]
return self.record.__class__.__name__ == name
return [(None, '')] + list(filter(func,
((m, m) for m in self._get_methods())))
@classmethod
def get(cls, shortened_id):
id = cls._expand(shortened_id)
return cls.search([('id', '=', id)], limit=1)[0]
def access(self, **values):
pool = Pool()
URLAccess = pool.get('web.shortened_url.access')
URLAccess(url=self, **values).save()
if self.record and self.method:
model, method = self.method.split('|')
if model == self.record.__class__.__name__:
method = getattr(self.record.__class__.__queue__, method)
method([self.record])
return self.redirect_url
@staticmethod
def _shorten(integer):
"Turns an integer into a string in a given alphabet"
key_part = []
while integer:
integer, remainder = divmod(integer, len(ALPHABET))
key_part.append(ALPHABET[remainder])
else:
if not key_part:
key_part.append('0')
return ''.join(reversed(key_part))
@staticmethod
def _expand(key):
"Turns a key from a given alphabet to the corresponding ID"
return int(key, len(ALPHABET))
class URLAccess(ModelSQL):
__name__ = 'web.shortened_url.access'
url = fields.Many2One('web.shortened_url', "URL", required=True)