99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
# passerelle - uniform access to multiple data sources and services
|
|
# Copyright (C) 2016 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 import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from .models import Query, get_code, identifier_re
|
|
|
|
|
|
class QueryForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Query
|
|
widgets = {
|
|
'resource': forms.HiddenInput(),
|
|
'filters': forms.Textarea(attrs={'rows': 2}),
|
|
'projections': forms.Textarea(attrs={'rows': 2}),
|
|
'order': forms.Textarea(attrs={'rows': 2}),
|
|
'distinct': forms.Textarea(attrs={'rows': 2}),
|
|
}
|
|
fields = '__all__'
|
|
|
|
def clean_lines_of_expressions(self, lines, named=False):
|
|
if not lines:
|
|
return lines
|
|
errors = []
|
|
for i, line in enumerate(lines.splitlines()):
|
|
if named:
|
|
line = line.split(':', 1)
|
|
if len(line) != 2:
|
|
errors.append(
|
|
ValidationError(
|
|
_(
|
|
'Syntax error line %d: each line must be prefixed '
|
|
'with an identifier followed by a colon.'
|
|
)
|
|
% (i + 1)
|
|
)
|
|
)
|
|
continue
|
|
name, line = line
|
|
if not identifier_re.match(name):
|
|
errors.append(
|
|
ValidationError(
|
|
_(
|
|
'Syntax error line %d: invalid identifier, '
|
|
'it must starts with a letter and only '
|
|
'contains letters, digits and _.'
|
|
)
|
|
% (i + 1)
|
|
)
|
|
)
|
|
continue
|
|
try:
|
|
get_code(line)
|
|
except SyntaxError as e:
|
|
errors.append(
|
|
ValidationError(
|
|
_('Syntax error line %(line)d at character %(character)d')
|
|
% {'line': i + 1, 'character': e.offset}
|
|
)
|
|
)
|
|
if errors:
|
|
raise ValidationError(errors)
|
|
return lines
|
|
|
|
def clean_filters(self):
|
|
return self.clean_lines_of_expressions(self.data.get('filters'))
|
|
|
|
def clean_projections(self):
|
|
return self.clean_lines_of_expressions(self.data.get('projections'), named=True)
|
|
|
|
def clean_order(self):
|
|
return self.clean_lines_of_expressions(self.data.get('order'))
|
|
|
|
def clean_distinct(self):
|
|
return self.clean_lines_of_expressions(self.data.get('distinct'))
|
|
|
|
def clean_structure(self):
|
|
if self.data.get('structure') == 'keyed-distinct':
|
|
if not self.cleaned_data.get('distinct'):
|
|
raise ValidationError(_('Keyed dictionary requires a single distinct column.'))
|
|
if len(self.cleaned_data.get('distinct').strip().splitlines()) > 1:
|
|
raise ValidationError(_('Keyed dictionary requires a single distinct column.'))
|
|
return self.data.get('structure')
|