Compare commits
41 Commits
wip/66993-
...
main
Author | SHA1 | Date |
---|---|---|
Thomas NOËL | 83f99fafee | |
Lauréline Guérin | e68e6e7ff9 | |
Frédéric Péters | 6ad1cc0911 | |
Benjamin Dauvergne | 7bb34ba534 | |
Emmanuel Cazenave | e912e4c687 | |
Frédéric Péters | e1e6d17d2f | |
Lauréline Guérin | 063f03c491 | |
Lauréline Guérin | 6e9d0a8b63 | |
Frédéric Péters | b32771096e | |
Frédéric Péters | a77c14e614 | |
Frédéric Péters | bbc215bfb6 | |
Frédéric Péters | 1b36a580ea | |
Valentin Deniaud | 542b25d7e0 | |
Valentin Deniaud | 1684993755 | |
Valentin Deniaud | c3534c25bf | |
Lauréline Guérin | d232a663e1 | |
Frédéric Péters | 9d4cf4f53b | |
Frédéric Péters | ea5ec534d5 | |
Frédéric Péters | d9796d27c7 | |
Frédéric Péters | efa1342a42 | |
Frédéric Péters | b458b1ceb1 | |
Frédéric Péters | a9d0b2f3f0 | |
Frédéric Péters | 4bfda4f828 | |
Lauréline Guérin | bdc8a59ff0 | |
Lauréline Guérin | c1e3cba42f | |
Lauréline Guérin | b41e397d02 | |
Lauréline Guérin | efae69e50d | |
Lauréline Guérin | 4141f48cd7 | |
Lauréline Guérin | 9bc4981677 | |
Agate | 1c7d91bb19 | |
Frédéric Péters | 1abf7c5d76 | |
Lauréline Guérin | 04f16f64ee | |
Frédéric Péters | ee9e74ce80 | |
Frédéric Péters | 4b16a94262 | |
Lauréline Guérin | 8f6fd7b2fe | |
Lauréline Guérin | f0a435dd8a | |
Lauréline Guérin | 77b277fc63 | |
Lauréline Guérin | c5b89d13a2 | |
Lauréline Guérin | a49ab97480 | |
Frédéric Péters | df0a449167 | |
Lauréline Guérin | 270e0b51dc |
|
@ -0,0 +1,2 @@
|
|||
# misc: apply double-quote-string-fixer (#79788)
|
||||
1684993755341e819948e9a3f81d54bc364bab68
|
|
@ -1,3 +1,4 @@
|
|||
**/django.mo
|
||||
*.pyc
|
||||
*.egg-info
|
||||
.pytest_cache/
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: double-quote-string-fixer
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: ['--target-version', 'py37', '--skip-string-normalization', '--line-length', '110']
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.7.0
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ['--profile', 'black', '--line-length', '110']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.20.0
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ['--keep-percent-format', '--py37-plus']
|
||||
- repo: https://git.entrouvert.org/pre-commit-debian.git
|
||||
rev: v0.3
|
||||
hooks:
|
||||
- id: pre-commit-debian
|
||||
|
|
|
@ -19,10 +19,18 @@ pipeline {
|
|||
stage('Packaging') {
|
||||
steps {
|
||||
script {
|
||||
if (env.JOB_NAME == 'publik-django-templatetags' && env.GIT_BRANCH == 'origin/main') {
|
||||
sh 'sudo -H -u eobuilder /usr/local/bin/eobuilder -d buster,bullseye publik-django-templatetags'
|
||||
env.SHORT_JOB_NAME=sh(
|
||||
returnStdout: true,
|
||||
// given JOB_NAME=gitea/project/PR-46, returns project
|
||||
// given JOB_NAME=project/main, returns project
|
||||
script: '''
|
||||
echo "${JOB_NAME}" | sed "s/gitea\\///" | awk -F/ '{print $1}'
|
||||
'''
|
||||
).trim()
|
||||
if (env.GIT_BRANCH == 'main' || env.GIT_BRANCH == 'origin/main') {
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d bullseye,bookworm ${SHORT_JOB_NAME}"
|
||||
} else if (env.GIT_BRANCH.startsWith('hotfix/')) {
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d buster,bullseye --branch ${env.GIT_BRANCH} --hotfix publik-django-templatetags"
|
||||
sh "sudo -H -u eobuilder /usr/local/bin/eobuilder -d bullseye,bookworm --branch ${env.GIT_BRANCH} --hotfix ${SHORT_JOB_NAME}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,3 +2,5 @@ include MANIFEST.in
|
|||
include COPYING
|
||||
include README
|
||||
include VERSION
|
||||
# locales
|
||||
recursive-include publik_django_templatetags/locale *.po *.mo
|
||||
|
|
|
@ -2,12 +2,17 @@ Source: publik-django-templatetags
|
|||
Maintainer: Entr’ouvert <info@entrouvert.com>
|
||||
Section: python
|
||||
Priority: optional
|
||||
Build-Depends: debhelper-compat (= 12), dh-python, python3-all, python3-setuptools, python3-django
|
||||
Build-Depends: debhelper-compat (= 12),
|
||||
dh-python,
|
||||
python3-all,
|
||||
python3-django,
|
||||
python3-setuptools,
|
||||
Standards-Version: 3.9.1
|
||||
|
||||
Package: python3-publik-django-templatetags
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, ${python3:Depends}
|
||||
Depends: ${misc:Depends},
|
||||
${python3:Depends},
|
||||
Description: Template tags and filters shared between Publik projects
|
||||
Django shared application with custom template tags and filters
|
||||
useful in multiple Publik modules.
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
# publik-django-templatetags French Translation.
|
||||
# Copyright (C) 2023 Entr'ouvert
|
||||
# This file is distributed under the same license as the publik-django-templatetags package.
|
||||
# Laureline Guerin <lguerin@entrouvert.com>, 2023.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: publik-django-templatetags 0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-08-17 17:48+0200\n"
|
||||
"PO-Revision-Date: 2023-08-17 17:49+0200\n"
|
||||
"Last-Translator: Laureline Guerin <lguerin@entrouvert.com>\n"
|
||||
"Language-Team: French\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(first)s and %(second)s"
|
||||
msgstr "%(first)s et %(second)s"
|
||||
|
||||
#: publik/utils.py
|
||||
msgid ", "
|
||||
msgstr ", "
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(total)s year"
|
||||
msgid_plural "%(total)s years"
|
||||
msgstr[0] "%(total)s année"
|
||||
msgstr[1] "%(total)s années"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(total)s month"
|
||||
msgid_plural "%(total)s months"
|
||||
msgstr[0] "%(total)s mois"
|
||||
msgstr[1] "%(total)s mois"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(total)s day"
|
||||
msgid_plural "%(total)s days"
|
||||
msgstr[0] "%(total)s jour"
|
||||
msgstr[1] "%(total)s jours"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(hours)sh%(minutes)02d"
|
||||
msgstr "%(hours)sh%(minutes)02d"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(hours)sh"
|
||||
msgstr "%(hours)sh"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(minutes)smin"
|
||||
msgstr "%(minutes)smin"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(total)s hour"
|
||||
msgid_plural "%(total)s hours"
|
||||
msgstr[0] "%(total)s heure"
|
||||
msgstr[1] "%(total)s heures"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(total)s minute"
|
||||
msgid_plural "%(total)s minutes"
|
||||
msgstr[0] "%(total)s minute"
|
||||
msgstr[1] "%(total)s minutes"
|
||||
|
||||
#: publik/utils.py
|
||||
#, python-format
|
||||
msgid "%(total)s second"
|
||||
msgid_plural "%(total)s seconds"
|
||||
msgstr[0] "%(total)s seconde"
|
||||
msgstr[1] "%(total)s secondes"
|
|
@ -14,14 +14,20 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import math
|
||||
import re
|
||||
import urllib.parse
|
||||
from decimal import Decimal
|
||||
from decimal import DivisionByZero as DecimalDivisionByZero
|
||||
from decimal import InvalidOperation as DecimalInvalidOperation
|
||||
|
||||
from django import template
|
||||
from django.template import defaultfilters
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from publik_django_templatetags.publik import utils
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -35,6 +41,8 @@ def get(obj, key):
|
|||
return obj[key]
|
||||
except (IndexError, KeyError, TypeError):
|
||||
return None
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
|
@ -51,19 +59,40 @@ def getlist(mapping, key):
|
|||
|
||||
@register.filter(name='list')
|
||||
def as_list(obj):
|
||||
return list(obj)
|
||||
try:
|
||||
return list(obj)
|
||||
except TypeError:
|
||||
return []
|
||||
|
||||
|
||||
@register.filter
|
||||
def split(string, separator=' '):
|
||||
return (force_text(string) or '').split(separator)
|
||||
return (force_str(string) or '').split(separator)
|
||||
|
||||
|
||||
@register.filter
|
||||
def removeprefix(string, prefix=None):
|
||||
if not string:
|
||||
return ''
|
||||
value = force_str(string)
|
||||
prefix = force_str(prefix)
|
||||
return value.removeprefix(prefix)
|
||||
|
||||
|
||||
@register.filter
|
||||
def removesuffix(string, suffix=None):
|
||||
if not string:
|
||||
return ''
|
||||
value = force_str(string)
|
||||
suffix = force_str(suffix)
|
||||
return value.removesuffix(suffix)
|
||||
|
||||
|
||||
@register.filter
|
||||
def first(value):
|
||||
try:
|
||||
return defaultfilters.first(value)
|
||||
except TypeError:
|
||||
except (TypeError, KeyError):
|
||||
return ''
|
||||
|
||||
|
||||
|
@ -71,17 +100,19 @@ def first(value):
|
|||
def last(value):
|
||||
try:
|
||||
return defaultfilters.last(value)
|
||||
except TypeError:
|
||||
except (TypeError, KeyError):
|
||||
return ''
|
||||
|
||||
|
||||
def parse_decimal(value, default=Decimal(0)):
|
||||
def parse_decimal(value, default=Decimal(0), do_raise=False):
|
||||
if isinstance(value, str):
|
||||
# replace , by . for French users comfort
|
||||
value = value.replace(',', '.')
|
||||
try:
|
||||
return Decimal(value).quantize(Decimal('1.0000')).normalize()
|
||||
except (ArithmeticError, TypeError):
|
||||
if do_raise:
|
||||
raise
|
||||
return default
|
||||
|
||||
|
||||
|
@ -98,19 +129,35 @@ def decimal(value, arg=None):
|
|||
def add(term1, term2):
|
||||
'''replace the "add" native django filter'''
|
||||
|
||||
# consider None content as the empty string
|
||||
if term1 is None:
|
||||
term1 = ''
|
||||
if term2 is None:
|
||||
term2 = ''
|
||||
term1_decimal = parse_decimal(term1, default=None)
|
||||
term2_decimal = parse_decimal(term2, default=None)
|
||||
|
||||
if term1_decimal is not None and term2_decimal is not None:
|
||||
return term1_decimal + term2_decimal
|
||||
if term1 == '' and term2_decimal is not None:
|
||||
return term2_decimal
|
||||
if term2 == '' and term1_decimal is not None:
|
||||
return term1_decimal
|
||||
# return available number if the other term is the empty string
|
||||
if term1 == '':
|
||||
try:
|
||||
return parse_decimal(term2, do_raise=True)
|
||||
except (ArithmeticError, TypeError):
|
||||
pass
|
||||
if term2 == '':
|
||||
try:
|
||||
return parse_decimal(term1, do_raise=True)
|
||||
except (ArithmeticError, TypeError):
|
||||
pass
|
||||
|
||||
# compute addition if both terms are numbers
|
||||
try:
|
||||
return parse_decimal(term1, do_raise=True) + parse_decimal(term2, do_raise=True)
|
||||
except (ArithmeticError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
# append to term1 if term1 is a list and not term2
|
||||
if isinstance(term1, list) and not isinstance(term2, list):
|
||||
return list(term1) + [term2]
|
||||
|
||||
# fallback to django add filter
|
||||
return defaultfilters.add(term1, term2)
|
||||
|
||||
|
||||
|
@ -159,3 +206,76 @@ def sum_(list_):
|
|||
return sum(parse_decimal(term) for term in list_)
|
||||
except TypeError: # list_ is not iterable
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def clamp(value, minmax):
|
||||
try:
|
||||
value = parse_decimal(value, do_raise=True)
|
||||
min_value, max_value = (parse_decimal(x, do_raise=True) for x in minmax.split())
|
||||
except (ArithmeticError, TypeError, ValueError):
|
||||
return ''
|
||||
return max(min_value, min(value, max_value))
|
||||
|
||||
|
||||
@register.filter
|
||||
def limit_low(value, min_value):
|
||||
try:
|
||||
return max(parse_decimal(value, do_raise=True), parse_decimal(min_value, do_raise=True))
|
||||
except (ArithmeticError, TypeError):
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def limit_high(value, max_value):
|
||||
try:
|
||||
return min(parse_decimal(value, do_raise=True), parse_decimal(max_value, do_raise=True))
|
||||
except (ArithmeticError, TypeError):
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def duration(value, arg='short'):
|
||||
if arg not in ('short', 'long'):
|
||||
return ''
|
||||
# value is expected to be a timedelta or a number of minutes
|
||||
if not isinstance(value, datetime.timedelta):
|
||||
try:
|
||||
value = datetime.timedelta(seconds=int(value) * 60)
|
||||
except (TypeError, ValueError):
|
||||
return ''
|
||||
return utils.seconds2humanduration(int(value.total_seconds()), short=bool(arg != 'long'))
|
||||
|
||||
|
||||
@register.filter(name='list')
|
||||
def list_(value):
|
||||
# turn a generator into a list
|
||||
if isinstance(value, collections.abc.Iterable) and not isinstance(value, (collections.abc.Mapping, str)):
|
||||
return list(value)
|
||||
else:
|
||||
return [value]
|
||||
|
||||
|
||||
@register.filter
|
||||
def with_auth(value, arg):
|
||||
parsed_url = urllib.parse.urlparse(value)
|
||||
new_netloc = '%s@%s' % (arg, parsed_url.netloc.rsplit('@', 1)[-1])
|
||||
return urllib.parse.urlunparse(parsed_url._replace(netloc=new_netloc))
|
||||
|
||||
|
||||
@register.filter
|
||||
def housenumber_number(housenumber):
|
||||
match = re.match(r'^\s*([0-9]+)(.*)$', force_str(housenumber))
|
||||
if not match:
|
||||
return ''
|
||||
number, btq = match.groups()
|
||||
return number
|
||||
|
||||
|
||||
@register.filter
|
||||
def housenumber_btq(housenumber):
|
||||
match = re.match(r'^\s*([0-9]+)(.*)$', force_str(housenumber))
|
||||
if not match:
|
||||
return ''
|
||||
number, btq = match.groups()
|
||||
return btq.strip()
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
# publik-django-templatetags
|
||||
# Copyright (C) 2023 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext_lazy
|
||||
|
||||
_minute = 60
|
||||
_hour = 60 * 60
|
||||
_day = _hour * 24
|
||||
_month = _day * 31
|
||||
_year = int(_day * 365.25)
|
||||
|
||||
|
||||
def list2human(stringlist):
|
||||
'''Transform a string list to human enumeration'''
|
||||
beginning = stringlist[:-1]
|
||||
if not beginning:
|
||||
return ''.join(stringlist)
|
||||
return _('%(first)s and %(second)s') % {'first': _(', ').join(beginning), 'second': stringlist[-1]}
|
||||
|
||||
|
||||
def seconds2humanduration(seconds, short=False):
|
||||
"""Convert a time range in seconds to a human string representation"""
|
||||
if not isinstance(seconds, int):
|
||||
return ''
|
||||
|
||||
if not short:
|
||||
years = int(seconds / _year)
|
||||
seconds = seconds - _year * years
|
||||
months = int(seconds / _month)
|
||||
seconds = seconds - _month * months
|
||||
days = int(seconds / _day)
|
||||
seconds = seconds - _day * days
|
||||
hours = int(seconds / _hour)
|
||||
seconds = seconds - _hour * hours
|
||||
minutes = int(seconds / _minute)
|
||||
seconds = seconds - _minute * minutes
|
||||
human = []
|
||||
if not short:
|
||||
if years:
|
||||
human.append(ngettext_lazy('%(total)s year', '%(total)s years', years) % {'total': years})
|
||||
if months:
|
||||
human.append(ngettext_lazy('%(total)s month', '%(total)s months', months) % {'total': months})
|
||||
if days:
|
||||
human.append(ngettext_lazy('%(total)s day', '%(total)s days', days) % {'total': days})
|
||||
if short:
|
||||
if hours and minutes:
|
||||
human.append(_('%(hours)sh%(minutes)02d') % {'hours': hours, 'minutes': minutes})
|
||||
elif hours:
|
||||
human.append(_('%(hours)sh') % {'hours': hours})
|
||||
elif minutes:
|
||||
human.append(_('%(minutes)smin') % {'minutes': minutes})
|
||||
return list2human(human)
|
||||
else:
|
||||
if hours:
|
||||
human.append(ngettext_lazy('%(total)s hour', '%(total)s hours', hours) % {'total': hours})
|
||||
if minutes:
|
||||
human.append(ngettext_lazy('%(total)s minute', '%(total)s minutes', minutes) % {'total': minutes})
|
||||
if seconds:
|
||||
human.append(ngettext_lazy('%(total)s second', '%(total)s seconds', seconds) % {'total': seconds})
|
||||
return list2human(human)
|
|
@ -14,7 +14,11 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
|
||||
from django.utils.dateparse import parse_date, parse_datetime
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.timezone import is_naive, make_aware
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from publik_django_templatetags.utils.requests_wrapper import requests
|
||||
|
@ -33,13 +37,16 @@ def get_default_wcs_service_key():
|
|||
|
||||
|
||||
class LazyCardDefObjectsManager:
|
||||
def __init__(self, service_key, card_id, custom_view_id=None, filters=None, user=Ellipsis):
|
||||
def __init__(
|
||||
self, service_key, card_id, custom_view_id=None, filters=None, geo_center=None, user=Ellipsis
|
||||
):
|
||||
self._service_key = service_key
|
||||
self._card_id = card_id
|
||||
self._custom_view_id = custom_view_id
|
||||
|
||||
self._filters = filters or {}
|
||||
self._user = user
|
||||
self._geo_center = geo_center or {}
|
||||
|
||||
self._cached_resultset = None
|
||||
|
||||
|
@ -49,6 +56,7 @@ class LazyCardDefObjectsManager:
|
|||
card_id=self._card_id,
|
||||
custom_view_id=self._custom_view_id,
|
||||
filters=self._filters,
|
||||
geo_center=self._geo_center,
|
||||
user=self._user,
|
||||
)
|
||||
|
||||
|
@ -69,6 +77,54 @@ class LazyCardDefObjectsManager:
|
|||
qs._filters['full'] = 'on'
|
||||
return qs
|
||||
|
||||
def include_fields(self):
|
||||
qs = self._clone()
|
||||
qs._filters['include-fields'] = 'on'
|
||||
return qs
|
||||
|
||||
def include_evolution(self):
|
||||
qs = self._clone()
|
||||
qs._filters['include-evolution'] = 'on'
|
||||
return qs
|
||||
|
||||
def include_roles(self):
|
||||
qs = self._clone()
|
||||
qs._filters['include-roles'] = 'on'
|
||||
return qs
|
||||
|
||||
def include_submission(self):
|
||||
qs = self._clone()
|
||||
qs._filters['include-submission'] = 'on'
|
||||
return qs
|
||||
|
||||
def include_workflow(self):
|
||||
qs = self._clone()
|
||||
qs._filters['include-workflow'] = 'on'
|
||||
return qs
|
||||
|
||||
def include_workflow_data(self):
|
||||
qs = self._clone()
|
||||
qs._filters['include-workflow-data'] = 'on'
|
||||
return qs
|
||||
|
||||
def get_at(self, value):
|
||||
qs = self._clone()
|
||||
if isinstance(value, str):
|
||||
parsed = parse_datetime(value)
|
||||
if not parsed:
|
||||
parsed = parse_date(value)
|
||||
if parsed:
|
||||
value = parsed
|
||||
if isinstance(value, datetime.datetime):
|
||||
if is_naive(value):
|
||||
value = make_aware(value)
|
||||
value = value.isoformat()
|
||||
elif isinstance(value, datetime.date):
|
||||
value = make_aware(datetime.datetime.combine(value, datetime.datetime.min.time())).isoformat()
|
||||
if value:
|
||||
qs._filters['at'] = value
|
||||
return qs
|
||||
|
||||
def access_control(self, user):
|
||||
qs = self._clone()
|
||||
qs._user = user
|
||||
|
@ -90,31 +146,104 @@ class LazyCardDefObjectsManager:
|
|||
value = ''
|
||||
if isinstance(value, bool):
|
||||
value = str(value).lower()
|
||||
op = getattr(self, 'pending_op', 'eq')
|
||||
if self.pending_attr in ['internal_id', 'number', 'identifier', 'user', 'status', 'distance']:
|
||||
return getattr(self, 'filter_by_%s' % self.pending_attr)(value, op)
|
||||
qs._filters['filter-%s' % self.pending_attr] = value
|
||||
qs._filters['filter-%s-operator' % self.pending_attr] = op
|
||||
return qs
|
||||
|
||||
def filter_by_internal_id(self, internal_id):
|
||||
def apply_op(self, op):
|
||||
self.pending_op = op
|
||||
return self
|
||||
|
||||
def apply_eq(self):
|
||||
return self.apply_op('eq')
|
||||
|
||||
def apply_ne(self):
|
||||
return self.apply_op('ne')
|
||||
|
||||
def apply_lt(self):
|
||||
return self.apply_op('lt')
|
||||
|
||||
def apply_lte(self):
|
||||
return self.apply_op('lte')
|
||||
|
||||
def apply_gt(self):
|
||||
return self.apply_op('gt')
|
||||
|
||||
def apply_gte(self):
|
||||
return self.apply_op('gte')
|
||||
|
||||
def apply_in(self):
|
||||
return self.apply_op('in')
|
||||
|
||||
def apply_not_in(self):
|
||||
return self.apply_op('not_in')
|
||||
|
||||
def apply_between(self):
|
||||
return self.apply_op('between')
|
||||
|
||||
def apply_absent(self):
|
||||
self.apply_op('absent')
|
||||
return self.apply_filter_value('on')
|
||||
|
||||
def apply_existing(self):
|
||||
self.apply_op('existing')
|
||||
return self.apply_filter_value('on')
|
||||
|
||||
def filter_by_internal_id(self, internal_id, op='eq'):
|
||||
qs = self._clone()
|
||||
if internal_id:
|
||||
qs._filters['filter-internal-id'] = internal_id
|
||||
qs._filters['filter-internal-id-operator'] = op
|
||||
return qs
|
||||
|
||||
def filter_by_number(self, number):
|
||||
def filter_by_number(self, number, op='eq'):
|
||||
qs = self._clone()
|
||||
if number:
|
||||
qs._filters['filter-number'] = number
|
||||
return qs
|
||||
|
||||
def filter_by_user(self, user):
|
||||
def filter_by_identifier(self, identifier, op='eq'):
|
||||
qs = self._clone()
|
||||
if user and user.is_authenticated and user.get_name_id():
|
||||
qs._filters['filter-user-uuid'] = user.get_name_id()
|
||||
if identifier:
|
||||
qs._filters['filter-identifier'] = identifier
|
||||
return qs
|
||||
|
||||
def filter_by_status(self, status):
|
||||
def filter_by_user(self, user, op='eq'):
|
||||
qs = self._clone()
|
||||
if user:
|
||||
if hasattr(user, 'is_authenticated') and hasattr(user, 'get_name_id'):
|
||||
if user.is_authenticated and user.get_name_id():
|
||||
qs._filters['filter-user-uuid'] = user.get_name_id()
|
||||
elif isinstance(user, str):
|
||||
qs._filters['filter-user-uuid'] = user
|
||||
return qs
|
||||
|
||||
def filter_by_status(self, status, op='eq'):
|
||||
qs = self._clone()
|
||||
if status:
|
||||
qs._filters['filter'] = status
|
||||
if op in ['eq', 'ne']:
|
||||
qs._filters['filter-operator'] = op
|
||||
return qs
|
||||
|
||||
def filter_by_distance(self, distance, op=None):
|
||||
# distance do not currently support an operator
|
||||
qs = self._clone()
|
||||
if distance:
|
||||
qs._filters['filter-distance'] = distance
|
||||
return qs
|
||||
|
||||
def set_geo_center_lat(self, lat):
|
||||
qs = self._clone()
|
||||
qs._geo_center['center_lat'] = lat
|
||||
return qs
|
||||
|
||||
def set_geo_center_lon(self, lon):
|
||||
qs = self._clone()
|
||||
qs._geo_center['center_lon'] = lon
|
||||
return qs
|
||||
|
||||
def _get_results_from_wcs(self):
|
||||
|
@ -128,6 +257,8 @@ class LazyCardDefObjectsManager:
|
|||
if self._filters:
|
||||
query = urlencode(self._filters)
|
||||
api_url += '?%s' % query
|
||||
if 'center_lat' in self._geo_center and 'center_lon' in self._geo_center:
|
||||
api_url += '&' + urlencode(self._geo_center)
|
||||
without_user = self._user is Ellipsis # not set
|
||||
try:
|
||||
response = requests.get(
|
||||
|
|
|
@ -21,59 +21,268 @@ register = template.Library()
|
|||
|
||||
@register.filter
|
||||
def objects(cards, slug):
|
||||
return getattr(cards, slug).objects
|
||||
try:
|
||||
return getattr(cards, slug).objects
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def with_custom_view(queryset, custom_view_id):
|
||||
return queryset.with_custom_view(custom_view_id)
|
||||
try:
|
||||
return queryset.with_custom_view(custom_view_id)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_full(queryset):
|
||||
return queryset.get_full()
|
||||
try:
|
||||
return queryset.get_full()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def include_fields(queryset):
|
||||
try:
|
||||
return queryset.include_fields()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def include_evolution(queryset):
|
||||
try:
|
||||
return queryset.include_evolution()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def include_roles(queryset):
|
||||
try:
|
||||
return queryset.include_roles()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def include_submission(queryset):
|
||||
try:
|
||||
return queryset.include_submission()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def include_workflow(queryset):
|
||||
try:
|
||||
return queryset.include_workflow()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def include_workflow_data(queryset):
|
||||
try:
|
||||
return queryset.include_workflow_data()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_at(queryset, value):
|
||||
return queryset.get_at(value)
|
||||
|
||||
|
||||
@register.filter
|
||||
def access_control(queryset, user):
|
||||
return queryset.access_control(user)
|
||||
try:
|
||||
return queryset.access_control(user)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def count(queryset):
|
||||
return queryset.count
|
||||
try:
|
||||
return queryset.count
|
||||
except AttributeError:
|
||||
return 0
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_by(queryset, attribute):
|
||||
return queryset.filter_by(attribute)
|
||||
try:
|
||||
return queryset.filter_by(attribute)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_value(queryset, value):
|
||||
return queryset.apply_filter_value(value)
|
||||
try:
|
||||
return queryset.apply_filter_value(value)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_by_internal_id(queryset, internal_id):
|
||||
return queryset.filter_by_internal_id(internal_id)
|
||||
try:
|
||||
return queryset.filter_by_internal_id(internal_id)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_by_number(queryset, number):
|
||||
return queryset.filter_by_number(number)
|
||||
try:
|
||||
return queryset.filter_by_number(number)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_by_identifier(queryset, identifier):
|
||||
try:
|
||||
return queryset.filter_by_identifier(identifier)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_by_user(queryset, user):
|
||||
return queryset.filter_by_user(user)
|
||||
try:
|
||||
return queryset.filter_by_user(user)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_by_status(queryset, status):
|
||||
return queryset.filter_by_status(status)
|
||||
try:
|
||||
return queryset.filter_by_status(status)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name='equal')
|
||||
def eq(queryset):
|
||||
try:
|
||||
return queryset.apply_eq()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name='not_equal')
|
||||
def ne(queryset):
|
||||
try:
|
||||
return queryset.apply_ne()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name='less_than')
|
||||
def lt(queryset):
|
||||
try:
|
||||
return queryset.apply_lt()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name='less_than_or_equal')
|
||||
def lte(queryset):
|
||||
try:
|
||||
return queryset.apply_lte()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name='greater_than')
|
||||
def gt(queryset):
|
||||
try:
|
||||
return queryset.apply_gt()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name='greater_than_or_equal')
|
||||
def gte(queryset):
|
||||
try:
|
||||
return queryset.apply_gte()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name='in')
|
||||
def _in(queryset):
|
||||
try:
|
||||
return queryset.apply_in()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter()
|
||||
def not_in(queryset):
|
||||
try:
|
||||
return queryset.apply_not_in()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter()
|
||||
def between(queryset):
|
||||
try:
|
||||
return queryset.apply_between()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter()
|
||||
def absent(queryset):
|
||||
try:
|
||||
return queryset.apply_absent()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter()
|
||||
def existing(queryset):
|
||||
try:
|
||||
return queryset.apply_existing()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def order_by(queryset, attribute):
|
||||
return queryset.order_by(attribute)
|
||||
try:
|
||||
return queryset.order_by(attribute)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def filter_by_distance(queryset, distance):
|
||||
try:
|
||||
return queryset.filter_by_distance(distance)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def set_geo_center_lat(queryset, lat):
|
||||
try:
|
||||
return queryset.set_geo_center_lat(lat)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def set_geo_center_lon(queryset, lon):
|
||||
try:
|
||||
return queryset.set_geo_center_lon(lon)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
|
40
setup.py
40
setup.py
|
@ -40,13 +40,39 @@ def get_version():
|
|||
real_number, commit_count, commit_hash = result.split('-', 2)
|
||||
version = '%s.post%s+%s' % (real_number, commit_count, commit_hash)
|
||||
else:
|
||||
version = result
|
||||
version = result.replace('.dirty', '+dirty')
|
||||
return version
|
||||
else:
|
||||
return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
|
||||
return '0.0'
|
||||
|
||||
|
||||
class make_translations(Command):
|
||||
description = 'make message catalogs via django makemessages'
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
curdir = os.getcwd()
|
||||
try:
|
||||
from django.core.management import call_command
|
||||
|
||||
for path, dirs, files in os.walk('publik_django_templatetags'):
|
||||
if 'locale' not in dirs:
|
||||
continue
|
||||
os.chdir(os.path.realpath(path))
|
||||
call_command('makemessages', '-l', 'fr', '--add-location', 'file', '--no-obsolete')
|
||||
except ImportError:
|
||||
sys.stderr.write('!!! Please install Django >= 2.2 to make translations\n')
|
||||
finally:
|
||||
os.chdir(curdir)
|
||||
|
||||
|
||||
class compile_translations(Command):
|
||||
description = 'compile message catalogs to MO files via django compilemessages'
|
||||
user_options = []
|
||||
|
@ -58,18 +84,19 @@ class compile_translations(Command):
|
|||
pass
|
||||
|
||||
def run(self):
|
||||
curdir = os.getcwd()
|
||||
try:
|
||||
from django.core.management import call_command
|
||||
|
||||
for path, dirs, files in os.walk('publik_django_templatetags'):
|
||||
if 'locale' not in dirs:
|
||||
continue
|
||||
curdir = os.getcwd()
|
||||
os.chdir(os.path.realpath(path))
|
||||
call_command('compilemessages')
|
||||
os.chdir(curdir)
|
||||
except ImportError:
|
||||
sys.stderr.write('!!! Please install Django >= 2.2 to build translations\n')
|
||||
finally:
|
||||
os.chdir(curdir)
|
||||
|
||||
|
||||
class build(_build):
|
||||
|
@ -101,12 +128,17 @@ setup(
|
|||
'Programming Language :: Python',
|
||||
],
|
||||
install_requires=[
|
||||
'django',
|
||||
'django>=3.2, <3.3',
|
||||
'requests',
|
||||
'urllib3<2',
|
||||
],
|
||||
setup_requires=[
|
||||
'django>=2.2, <3.3',
|
||||
],
|
||||
zip_safe=False,
|
||||
cmdclass={
|
||||
'build': build,
|
||||
'make_translations': make_translations,
|
||||
'compile_translations': compile_translations,
|
||||
'install_lib': install_lib,
|
||||
'sdist': eo_sdist,
|
||||
|
|
|
@ -4,7 +4,7 @@ DATABASES = {
|
|||
'default': {
|
||||
'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.postgresql_psycopg2'),
|
||||
'NAME': 'publik-django-templatetags-test-%s'
|
||||
% os.environ.get("BRANCH_NAME", "").replace('/', '-')[:45],
|
||||
% os.environ.get('BRANCH_NAME', '').replace('/', '-')[:45],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,12 +51,12 @@ REQUESTS_TIMEOUT = 25
|
|||
DEBUG = True
|
||||
USE_TZ = True
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sites",
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sites',
|
||||
]
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_URL = '/static/'
|
||||
SITE_ID = 1
|
||||
MIDDLEWARE_CLASSES = ()
|
||||
LOGGING = {}
|
||||
SECRET_KEY = "yay"
|
||||
SECRET_KEY = 'yay'
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import html
|
||||
|
||||
from django.template import Context, Template
|
||||
|
||||
|
||||
|
@ -63,6 +65,36 @@ def test_split():
|
|||
assert t.render(Context({'plop': 42})) == '42 '
|
||||
|
||||
|
||||
def test_removeprefix():
|
||||
t = Template('{{ foo|removeprefix }}')
|
||||
assert t.render(Context({})) == ''
|
||||
assert t.render(Context({'foo': None})) == ''
|
||||
assert t.render(Context({'foo': 'foo bar'})) == 'foo bar'
|
||||
t = Template('{{ foo|removeprefix:"" }}')
|
||||
assert t.render(Context({'foo': 'foo bar'})) == 'foo bar'
|
||||
t = Template('{{ foo|removeprefix:"XY" }}')
|
||||
assert t.render(Context({'foo': 'XYfoo barXY'})) == 'foo barXY'
|
||||
assert t.render(Context({'foo': 'foo bar'})) == 'foo bar'
|
||||
assert t.render(Context({'foo': 'xyfoo barXY'})) == 'xyfoo barXY'
|
||||
assert t.render(Context({'foo': ' XYfoo barXY'})) == ' XYfoo barXY'
|
||||
assert t.render(Context({'foo': 'XYXYfoo barXY'})) == 'XYfoo barXY'
|
||||
|
||||
|
||||
def test_removesuffix():
|
||||
t = Template('{{ foo|removesuffix }}')
|
||||
assert t.render(Context()) == ''
|
||||
assert t.render(Context({'foo': None})) == ''
|
||||
assert t.render(Context({'foo': 'foo bar'})) == 'foo bar'
|
||||
t = Template('{{ foo|removesuffix:"" }}')
|
||||
assert t.render(Context({'foo': 'foo bar'})) == 'foo bar'
|
||||
t = Template('{{ foo|removesuffix:"XY" }}')
|
||||
assert t.render(Context({'foo': 'XYfoo barXY'})) == 'XYfoo bar'
|
||||
assert t.render(Context({'foo': 'foo bar'})) == 'foo bar'
|
||||
assert t.render(Context({'foo': 'XYfoo barxy'})) == 'XYfoo barxy'
|
||||
assert t.render(Context({'foo': 'XYfoo barXY '})) == 'XYfoo barXY '
|
||||
assert t.render(Context({'foo': 'XYfoo barXYXY'})) == 'XYfoo barXY'
|
||||
|
||||
|
||||
def test_first():
|
||||
t = Template('{{ foo|first }}')
|
||||
|
||||
|
@ -78,6 +110,9 @@ def test_first():
|
|||
context = Context({'foo': None})
|
||||
assert t.render(context) == ''
|
||||
|
||||
context = Context({'foo': {}})
|
||||
assert t.render(context) == ''
|
||||
|
||||
|
||||
def test_last():
|
||||
t = Template('{{ foo|last }}')
|
||||
|
@ -94,6 +129,9 @@ def test_last():
|
|||
context = Context({'foo': None})
|
||||
assert t.render(context) == ''
|
||||
|
||||
context = Context({'foo': {}})
|
||||
assert t.render(context) == ''
|
||||
|
||||
|
||||
def test_decimal():
|
||||
tmpl = Template('{{ plop|decimal }}')
|
||||
|
@ -142,8 +180,8 @@ def test_add():
|
|||
# using strings
|
||||
assert tmpl.render(Context({'term1': '1.1', 'term2': 0})) == '1.1'
|
||||
assert tmpl.render(Context({'term1': 'not a number', 'term2': 1.2})) == ''
|
||||
assert tmpl.render(Context({'term1': 0.3, 'term2': "1"})) == '1.3'
|
||||
assert tmpl.render(Context({'term1': 1.4, 'term2': "not a number"})) == ''
|
||||
assert tmpl.render(Context({'term1': 0.3, 'term2': '1'})) == '1.3'
|
||||
assert tmpl.render(Context({'term1': 1.4, 'term2': 'not a number'})) == ''
|
||||
|
||||
# add
|
||||
assert tmpl.render(Context({'term1': 4, 'term2': -0.9})) == '3.1'
|
||||
|
@ -298,3 +336,147 @@ def test_sum():
|
|||
assert t.render(Context({'list': None})) == '' # list is not iterable
|
||||
assert t.render(Context({'list': '123'})) == '' # consider string as not iterable
|
||||
assert t.render(Context()) == ''
|
||||
|
||||
|
||||
def test_clamp_templatetag():
|
||||
tmpl = Template('{{ value|clamp:"3.5 5.5" }}')
|
||||
assert tmpl.render(Context({'value': 4})) == '4'
|
||||
assert tmpl.render(Context({'value': 6})) == '5.5'
|
||||
assert tmpl.render(Context({'value': 3})) == '3.5'
|
||||
assert tmpl.render(Context({'value': 'abc'})) == ''
|
||||
assert tmpl.render(Context({'value': None})) == ''
|
||||
|
||||
tmpl = Template('{{ value|clamp:"3.5 5.5 7.5" }}')
|
||||
assert tmpl.render(Context({'value': 4})) == ''
|
||||
|
||||
tmpl = Template('{{ value|clamp:"a b" }}')
|
||||
assert tmpl.render(Context({'value': 4})) == ''
|
||||
|
||||
|
||||
def test_limit_templatetags():
|
||||
for v in (3.5, '"3.5"', 'xxx'):
|
||||
tmpl = Template('{{ value|limit_low:%s }}' % v)
|
||||
assert tmpl.render(Context({'value': 4, 'xxx': 3.5})) == '4'
|
||||
assert tmpl.render(Context({'value': 3, 'xxx': 3.5})) == '3.5'
|
||||
assert tmpl.render(Context({'value': 'abc', 'xxx': 3.5})) == ''
|
||||
assert tmpl.render(Context({'value': None, 'xxx': 3.5})) == ''
|
||||
if v == 'xxx':
|
||||
assert tmpl.render(Context({'value': 3, 'xxx': 'plop'})) == ''
|
||||
|
||||
tmpl = Template('{{ value|limit_high:%s }}' % v)
|
||||
assert tmpl.render(Context({'value': 4, 'xxx': 3.5})) == '3.5'
|
||||
assert tmpl.render(Context({'value': 3, 'xxx': 3.5})) == '3'
|
||||
assert tmpl.render(Context({'value': 'abc', 'xxx': 3.5})) == ''
|
||||
assert tmpl.render(Context({'value': None, 'xxx': 3.5})) == ''
|
||||
if v == 'xxx':
|
||||
assert tmpl.render(Context({'value': 3, 'xxx': 'plop'})) == ''
|
||||
|
||||
|
||||
def test_duration():
|
||||
context = Context({'value': 2})
|
||||
assert Template('{{ value|duration }}').render(context) == '2min'
|
||||
assert Template('{{ value|duration:"long" }}').render(context) == '2 minutes'
|
||||
|
||||
context = Context({'value': 80})
|
||||
assert Template('{{ value|duration }}').render(context) == '1h20'
|
||||
assert Template('{{ value|duration:"long" }}').render(context) == '1 hour and 20 minutes'
|
||||
|
||||
context = Context({'value': 40})
|
||||
assert Template('{{ value|duration }}').render(context) == '40min'
|
||||
assert Template('{{ value|duration:"long" }}').render(context) == '40 minutes'
|
||||
|
||||
context = Context({'value': 120})
|
||||
assert Template('{{ value|duration }}').render(context) == '2h'
|
||||
assert Template('{{ value|duration:"long" }}').render(context) == '2 hours'
|
||||
|
||||
context = Context({'value': 1510})
|
||||
assert Template('{{ value|duration }}').render(context) == '1 day and 1h10'
|
||||
assert Template('{{ value|duration:"long" }}').render(context) == '1 day, 1 hour and 10 minutes'
|
||||
|
||||
context = Context({'value': 61})
|
||||
assert Template('{{ value|duration }}').render(context) == '1h01'
|
||||
|
||||
context = Context({'value': 'xx'})
|
||||
assert Template('{{ value|duration }}').render(context) == ''
|
||||
assert Template('{{ value|duration:"long" }}').render(context) == ''
|
||||
|
||||
context = Context({'value': 166_660})
|
||||
assert (
|
||||
Template('{{ value|duration:"long" }}').render(context)
|
||||
== '3 months, 22 days, 17 hours and 40 minutes'
|
||||
)
|
||||
|
||||
context = Context({'value': 1_666_666})
|
||||
assert (
|
||||
Template('{{ value|duration:"long" }}').render(context)
|
||||
== '3 years, 1 month, 30 days, 15 hours and 46 minutes'
|
||||
)
|
||||
|
||||
|
||||
def test_convert_as_list():
|
||||
tmpl = Template('{{ foo|list|first }}')
|
||||
assert tmpl.render(Context({'foo': ['foo']})) == 'foo'
|
||||
|
||||
def list_generator():
|
||||
yield from range(5)
|
||||
|
||||
assert tmpl.render(Context({'foo': list_generator})) == '0'
|
||||
|
||||
def list_range():
|
||||
return range(5)
|
||||
|
||||
assert tmpl.render(Context({'foo': list_range})) == '0'
|
||||
|
||||
|
||||
def test_convert_as_list_with_add():
|
||||
tmpl = Template('{{ foo|list|add:bar|join:", " }}')
|
||||
assert tmpl.render(Context({'foo': [1, 2], 'bar': ['a', 'b']})) == '1, 2, a, b'
|
||||
assert tmpl.render(Context({'foo': [1, 2], 'bar': 'ab'})) == '1, 2, ab'
|
||||
assert tmpl.render(Context({'foo': 12, 'bar': ['a', 'b']})) == '12, a, b'
|
||||
assert tmpl.render(Context({'foo': 12, 'bar': 'ab'})) == '12, ab'
|
||||
assert html.unescape(tmpl.render(Context({'foo': [1, 2], 'bar': {'a': 'b'}}))) == "1, 2, {'a': 'b'}"
|
||||
assert html.unescape(tmpl.render(Context({'foo': {'a': 'b'}, 'bar': ['a', 'b']}))) == "{'a': 'b'}, a, b"
|
||||
|
||||
|
||||
def test_with_auth():
|
||||
context = Context({'service_url': 'https://www.example.net/api/whatever?x=y'})
|
||||
assert (
|
||||
Template('{{ service_url|with_auth:"username:password" }}').render(context)
|
||||
== 'https://username:password@www.example.net/api/whatever?x=y'
|
||||
)
|
||||
|
||||
context = Context({'service_url': 'https://a:b@www.example.net/api/whatever?x=y'})
|
||||
assert (
|
||||
Template('{{ service_url|with_auth:"username:password" }}').render(context)
|
||||
== 'https://username:password@www.example.net/api/whatever?x=y'
|
||||
)
|
||||
|
||||
|
||||
def test_housenumber():
|
||||
context = Context({'value': '42bis'})
|
||||
assert Template('{{ value|housenumber_number }}').render(context) == '42'
|
||||
assert Template('{{ value|housenumber_btq }}').render(context) == 'bis'
|
||||
|
||||
context = Context({'value': ' 42 bis '})
|
||||
assert Template('{{ value|housenumber_number }}').render(context) == '42'
|
||||
assert Template('{{ value|housenumber_btq }}').render(context) == 'bis'
|
||||
|
||||
context = Context({'value': '42 t 3 '})
|
||||
assert Template('{{ value|housenumber_number }}').render(context) == '42'
|
||||
assert Template('{{ value|housenumber_btq }}').render(context) == 't 3'
|
||||
|
||||
context = Context({'value': ' 42 '})
|
||||
assert Template('{{ value|housenumber_number }}').render(context) == '42'
|
||||
assert Template('{{ value|housenumber_btq }}').render(context) == ''
|
||||
|
||||
context = Context({'value': ' 42 34 bis '})
|
||||
assert Template('{{ value|housenumber_number }}').render(context) == '42'
|
||||
assert Template('{{ value|housenumber_btq }}').render(context) == '34 bis'
|
||||
|
||||
context = Context({'value': ' bis '})
|
||||
assert Template('{{ value|housenumber_number }}').render(context) == ''
|
||||
assert Template('{{ value|housenumber_btq }}').render(context) == ''
|
||||
|
||||
context = Context({'value': 42})
|
||||
assert Template('{{ value|housenumber_number }}').render(context) == '42'
|
||||
assert Template('{{ value|housenumber_btq }}').render(context) == ''
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import copy
|
||||
import datetime
|
||||
import json
|
||||
from unittest import mock
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import pytest
|
||||
from django.template import Context, Template
|
||||
|
@ -78,7 +80,7 @@ def test_objects(mock_send, settings, context, nocache):
|
|||
|
||||
# test filters evaluation
|
||||
t = Template('{% for card in cards|objects:"foo" %}{{ card.id }} {% endfor %}')
|
||||
assert t.render(context) == "1 2 "
|
||||
assert t.render(context) == '1 2 '
|
||||
assert mock_send.call_args_list[0][0][0].url.startswith(
|
||||
'http://127.0.0.1:8999/api/cards/foo/list?'
|
||||
) # primary service
|
||||
|
@ -110,6 +112,17 @@ def test_objects(mock_send, settings, context, nocache):
|
|||
t.render(context)
|
||||
assert mock_send.call_args_list[0][0][0].url.startswith('http://127.0.0.3:8999/api/cards/bar/list?')
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|get:"bar" }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
context = Context({}) # no cards in context
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"bar" }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_with_custom_view(mock_send, context, nocache):
|
||||
|
@ -134,6 +147,12 @@ def test_with_custom_view(mock_send, context, nocache):
|
|||
t.render(context)
|
||||
assert mock_send.call_args_list == [] # unknown, not evaluated
|
||||
|
||||
mock_send.reset_mock()
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|with_custom_view:"foobar"|list }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_full(mock_send, context, nocache):
|
||||
|
@ -145,6 +164,86 @@ def test_full(mock_send, context, nocache):
|
|||
t.render(context)
|
||||
assert 'full=on&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|get_full|list }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_include_filters(mock_send, context, nocache):
|
||||
for part in ['fields', 'evolution', 'roles', 'submission', 'workflow', 'workflow_data']:
|
||||
_filter = 'include_%s' % part
|
||||
param = _filter.replace('_', '-')
|
||||
t = Template('{{ cards|objects:"foo"|list }}')
|
||||
t.render(context)
|
||||
assert '%s=on&' % param not in mock_send.call_args_list[0][0][0].url
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s|list }}' % _filter)
|
||||
t.render(context)
|
||||
assert '%s=on&' % param in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|%s|list }}' % _filter)
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_at(mock_send, settings, freezer, context, nocache):
|
||||
settings.TIME_ZONE = 'Europe/Paris'
|
||||
freezer.move_to('2022-10-17 12:21')
|
||||
|
||||
def get_at_param(url):
|
||||
parsed = parse_qs(urlparse(url).query)
|
||||
if 'at' not in parsed:
|
||||
return
|
||||
return parsed['at'][0]
|
||||
|
||||
t = Template('{{ cards|objects:"foo"|list }}')
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) is None
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|get_at:""|list }}')
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) is None
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|get_at:none|list }}')
|
||||
context['none'] = None
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) is None
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|get_at:"bad-value"|list }}')
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) == 'bad-value'
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|get_at:"2022-10-17"|list }}')
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) == '2022-10-17T00:00:00+02:00'
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|get_at:"2022-10-17 23:20"|list }}')
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) == '2022-10-17T23:20:00+02:00'
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|get_at:a_date|list }}')
|
||||
context['a_date'] = datetime.date.today()
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) == '2022-10-17T00:00:00+02:00'
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|get_at:a_datetime|list }}')
|
||||
context['a_datetime'] = datetime.datetime.now()
|
||||
t.render(context)
|
||||
assert get_at_param(mock_send.call_args_list[0][0][0].url) == '2022-10-17T12:21:00+02:00'
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_errors(mock_send, context, nocache):
|
||||
|
@ -154,27 +253,27 @@ def test_errors(mock_send, context, nocache):
|
|||
mock_resp = Response()
|
||||
mock_resp.status_code = 500
|
||||
requests_get.return_value = mock_resp
|
||||
assert t.render(context) == "[]"
|
||||
assert t.render(context) == '[]'
|
||||
|
||||
with mock.patch('publik_django_templatetags.wcs.context_processors.requests.get') as requests_get:
|
||||
requests_get.side_effect = ConnectionError()
|
||||
requests_get.return_value = mock_resp
|
||||
assert t.render(context) == "[]"
|
||||
assert t.render(context) == '[]'
|
||||
|
||||
with mock.patch('publik_django_templatetags.wcs.context_processors.requests.get') as requests_get:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 404
|
||||
requests_get.return_value = mock_resp
|
||||
assert t.render(context) == "[]"
|
||||
assert t.render(context) == '[]'
|
||||
|
||||
mock_send.side_effect = lambda *a, **k: MockedRequestResponse(content=json.dumps({'err': 1}))
|
||||
assert t.render(context) == "[]"
|
||||
assert t.render(context) == '[]'
|
||||
|
||||
mock_send.side_effect = lambda *a, **k: MockedRequestResponse(content=json.dumps({}))
|
||||
assert t.render(context) == "[]"
|
||||
assert t.render(context) == '[]'
|
||||
|
||||
mock_send.side_effect = lambda *a, **k: MockedRequestResponse(content=json.dumps({'data': None}))
|
||||
assert t.render(context) == "[]"
|
||||
assert t.render(context) == '[]'
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
|
@ -229,11 +328,38 @@ def test_access_control(mock_send, context, nocache):
|
|||
assert 'NameID' not in mock_send.call_args_list[0][0][0].url
|
||||
assert 'email=foo%40example.net&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|access_control:request.user|list }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_count(mock_send, context, nocache):
|
||||
t = Template('{{ cards|objects:"foo"|count }}')
|
||||
assert t.render(context) == "2"
|
||||
assert t.render(context) == '2'
|
||||
|
||||
context = Context({'foo': None})
|
||||
t = Template('{{ foo|count }}')
|
||||
assert t.render(context) == '0'
|
||||
|
||||
|
||||
OPERATORS = [
|
||||
('equal', 'eq'),
|
||||
('not_equal', 'ne'),
|
||||
('less_than', 'lt'),
|
||||
('less_than_or_equal', 'lte'),
|
||||
('greater_than', 'gt'),
|
||||
('greater_than_or_equal', 'gte'),
|
||||
('in', 'in'),
|
||||
('not_in', 'not_in'),
|
||||
('between', 'between'),
|
||||
]
|
||||
OPERATORS_WITHOUT_VALUE = [
|
||||
('absent', 'absent'),
|
||||
('existing', 'existing'),
|
||||
]
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
|
@ -266,6 +392,41 @@ def test_filter(mock_send, context, nocache):
|
|||
t.render(context)
|
||||
assert 'filter-foo=&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
context['foobar'] = None
|
||||
for filter_op, api_op in OPERATORS:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by:"foo"|%s|filter_value:"bar"|list }}' % filter_op)
|
||||
t.render(context)
|
||||
assert 'filter-foo=bar&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-foo-operator=%s&' % api_op in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ foobar|%s }}' % filter_op)
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
for filter_op, api_op in OPERATORS_WITHOUT_VALUE:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by:"foo"|%s|list }}' % filter_op)
|
||||
t.render(context)
|
||||
assert 'filter-foo=on&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-foo-operator=%s&' % api_op in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ foobar|%s }}' % filter_op)
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ foobar|filter_by:"foo"|list }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ foobar|filter_value:"foo"|list }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_filter_by_internal_id(mock_send, context, nocache):
|
||||
|
@ -273,20 +434,36 @@ def test_filter_by_internal_id(mock_send, context, nocache):
|
|||
t.render(context)
|
||||
assert 'filter-internal-id' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_internal_id:None|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-internal-id' not in mock_send.call_args_list[0][0][0].url
|
||||
for tpl in ['filter_by_internal_id', 'filter_by:"internal_id"|filter_value']:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:None|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-internal-id' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:""|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-internal-id' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:"42"|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-internal-id=42&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for filter_op, api_op in OPERATORS:
|
||||
mock_send.reset_mock()
|
||||
t = Template(
|
||||
'{{ cards|objects:"foo"|filter_by:"internal_id"|%s|filter_value:"42"|list }}' % filter_op
|
||||
)
|
||||
t.render(context)
|
||||
assert 'filter-internal-id=42&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-internal-id-operator=%s&' % api_op in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_internal_id:""|list }}')
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|filter_by_internal_id:"42"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-internal-id' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_internal_id:"42"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-internal-id=42&' in mock_send.call_args_list[0][0][0].url
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
|
@ -295,42 +472,121 @@ def test_filter_by_number(mock_send, context, nocache):
|
|||
t.render(context)
|
||||
assert 'filter-number' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_number:None|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-number' not in mock_send.call_args_list[0][0][0].url
|
||||
for tpl in ['filter_by_number', 'filter_by:"number"|filter_value']:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:None|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-number' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:""|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-number' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:"42-35"|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-number=42-35&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for filter_op, api_op in OPERATORS:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by:"number"|%s|filter_value:"42-35"|list }}' % filter_op)
|
||||
t.render(context)
|
||||
assert 'filter-number=42-35&' in mock_send.call_args_list[0][0][0].url
|
||||
# not for this filter
|
||||
assert 'filter-number-operator=%s&' % api_op not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_number:""|list }}')
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|filter_by_number:"42"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-number' not in mock_send.call_args_list[0][0][0].url
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_filter_by_identifier(mock_send, context, nocache):
|
||||
t = Template('{{ cards|objects:"foo"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-identifier' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for tpl in ['filter_by_identifier', 'filter_by:"identifier"|filter_value']:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:None|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-identifier' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:""|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-identifier' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:"42-35"|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-identifier=42-35&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for filter_op, api_op in OPERATORS:
|
||||
mock_send.reset_mock()
|
||||
t = Template(
|
||||
'{{ cards|objects:"foo"|filter_by:"identifier"|%s|filter_value:"42-35"|list }}' % filter_op
|
||||
)
|
||||
t.render(context)
|
||||
assert 'filter-identifier=42-35&' in mock_send.call_args_list[0][0][0].url
|
||||
# not for this filter
|
||||
assert 'filter-identifier-operator=%s&' % api_op not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_number:"42-35"|list }}')
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|filter_by_identifier:"42"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-number=42-35&' in mock_send.call_args_list[0][0][0].url
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_filter_by_user(mock_send, context, nocache):
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_user:request.user|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid' not in mock_send.call_args_list[0][0][0].url
|
||||
context['nameid'] = 'zyx'
|
||||
for tpl in ['filter_by_user', 'filter_by:"user"|filter_value']:
|
||||
t = Template('{{ cards|objects:"foo"|%s:request.user|list }}' % tpl)
|
||||
mock_send.reset_mock()
|
||||
context['request'].user = None
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
context['request'].user = MockAnonymousUser()
|
||||
mock_send.reset_mock()
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid' not in mock_send.call_args_list[0][0][0].url
|
||||
context['request'].user = MockAnonymousUser()
|
||||
mock_send.reset_mock()
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
context['request'].user = MockUser()
|
||||
mock_send.reset_mock()
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid' not in mock_send.call_args_list[0][0][0].url
|
||||
context['request'].user = MockUser()
|
||||
mock_send.reset_mock()
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
context['request'].user = MockUserWithNameId()
|
||||
mock_send.reset_mock()
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid=xyz&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
t = Template('{{ cards|objects:"foo"|%s:nameid|list }}' % tpl)
|
||||
mock_send.reset_mock()
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid=zyx&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for filter_op, api_op in OPERATORS:
|
||||
mock_send.reset_mock()
|
||||
t = Template(
|
||||
'{{ cards|objects:"foo"|filter_by:"user"|%s|filter_value:request.user|list }}' % filter_op
|
||||
)
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid=xyz&' in mock_send.call_args_list[0][0][0].url
|
||||
# not for this filter
|
||||
assert 'filter-user-uuid-operator=%s&' % api_op not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
context['request'].user = MockUserWithNameId()
|
||||
mock_send.reset_mock()
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|filter_by_user:request.user|list }}')
|
||||
t.render(context)
|
||||
assert 'filter-user-uuid=xyz&' in mock_send.call_args_list[0][0][0].url
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
|
@ -338,43 +594,97 @@ def test_filter_by_status(mock_send, context, nocache):
|
|||
t = Template('{{ cards|objects:"foo"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter=&' not in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-operator=eq&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for tpl in ['filter_by_status', 'filter_by:"status"|filter_value']:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:None|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter=&' not in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-operator=eq&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:""|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter=&' not in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-operator=eq&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:"foobar"|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter=foobar&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-operator=eq&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for filter_op, api_op in OPERATORS:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by:"status"|%s|filter_value:"foobar"|list }}' % filter_op)
|
||||
t.render(context)
|
||||
assert 'filter=foobar&' in mock_send.call_args_list[0][0][0].url
|
||||
if api_op in ['eq', 'ne']:
|
||||
assert 'filter-operator=%s&' % api_op in mock_send.call_args_list[0][0][0].url
|
||||
else:
|
||||
assert 'filter-operator=%s&' % api_op not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_status:None|list }}')
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|filter_by_status:"foobar"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter=&' not in mock_send.call_args_list[0][0][0].url
|
||||
assert mock_send.call_args_list == []
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_status:""|list }}')
|
||||
t.render(context)
|
||||
assert 'filter=&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|filter_by_status:"foobar"|list }}')
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_filter_by_distance(mock_send, context, nocache):
|
||||
t = Template('{{ cards|objects:"foo"|list }}')
|
||||
t.render(context)
|
||||
assert 'filter=foobar&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-distance=&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
for tpl in ['filter_by_distance', 'filter_by:"distance"|filter_value']:
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:None|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-distance=&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:""|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-distance=&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template('{{ cards|objects:"foo"|%s:"10000"|list }}' % tpl)
|
||||
t.render(context)
|
||||
assert 'filter-distance=&' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
t = Template(
|
||||
'{{ cards|objects:"foo"|set_geo_center_lat:1|set_geo_center_lon:2|%s:"10000"|list }}' % tpl
|
||||
)
|
||||
t.render(context)
|
||||
assert 'center_lat=1&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'center_lon=2&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-distance=10000&' in mock_send.call_args_list[0][0][0].url
|
||||
assert 'filter-distance-operator' not in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_getlist(mock_send, context, nocache):
|
||||
t = Template('{% for v in cards|objects:"foo"|getlist:"id" %}{{ v }},{% endfor %}')
|
||||
t.render(context)
|
||||
assert t.render(context) == "1,2,"
|
||||
assert t.render(context) == '1,2,'
|
||||
t = Template('{% for v in cards|objects:"foo"|getlist:"fields" %}{{ v }},{% endfor %}')
|
||||
t.render(context)
|
||||
result = t.render(context)
|
||||
if ''' in result:
|
||||
# django 2.2
|
||||
assert result == "{'foo': 'bar'},{'foo': 'baz'},"
|
||||
assert result == '{'foo': 'bar'},{'foo': 'baz'},'
|
||||
else:
|
||||
# django 3.2
|
||||
assert result == "{'foo': 'bar'},{'foo': 'baz'},"
|
||||
assert result == '{'foo': 'bar'},{'foo': 'baz'},'
|
||||
t = Template('{% for v in cards|objects:"foo"|getlist:"fields"|getlist:"foo" %}{{ v }},{% endfor %}')
|
||||
t.render(context)
|
||||
assert t.render(context) == "bar,baz,"
|
||||
assert t.render(context) == 'bar,baz,'
|
||||
t = Template('{% for v in cards|objects:"foo"|getlist:"fields"|getlist:"unknown" %}{{ v }},{% endfor %}')
|
||||
t.render(context)
|
||||
assert t.render(context) == "None,None,"
|
||||
assert t.render(context) == 'None,None,'
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
|
@ -407,3 +717,9 @@ def test_order_by(mock_send, context, nocache):
|
|||
t = Template('{% for v in cards|objects:"foo"|order_by:""|order_by:"bar" %}{{ v }},{% endfor %}')
|
||||
t.render(context)
|
||||
assert 'order_by=bar' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
mock_send.reset_mock()
|
||||
context['foobar'] = None
|
||||
t = Template('{{ foobar|order_by:"foo" }}')
|
||||
t.render(context)
|
||||
assert mock_send.call_args_list == []
|
||||
|
|
8
tox.ini
8
tox.ini
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py3-django{22,32}-codestyle-coverage
|
||||
envlist = py3-django32-codestyle-coverage
|
||||
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/publik-django-templatetags/{env:BRANCH_NAME:}
|
||||
|
||||
[testenv]
|
||||
|
@ -11,14 +11,14 @@ setenv =
|
|||
JUNIT=--junitxml=junit-{envname}.xml
|
||||
coverage: COVERAGE=--cov-report xml --cov-report html --cov=publik_django_templatetags/
|
||||
deps =
|
||||
django22: django>=2.2,<2.3
|
||||
django32: django>=3.2,<3.3
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-django
|
||||
pytest-freezegun
|
||||
WebTest
|
||||
psycopg2-binary<2.9
|
||||
psycopg2<2.9
|
||||
psycopg2-binary
|
||||
psycopg2
|
||||
pre-commit
|
||||
commands =
|
||||
pytest {posargs: {env:JUNIT:} {env:COVERAGE:} tests/}
|
||||
|
|
Loading…
Reference in New Issue