Skip to content

Commit

Permalink
add validators
Browse files Browse the repository at this point in the history
  • Loading branch information
jlmadurga committed Apr 4, 2016
1 parent 1c0570b commit 66159a5
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 15 deletions.
51 changes: 51 additions & 0 deletions microbot/migrations/0015_auto_20160404_0449.py
@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-04-04 09:49
from __future__ import unicode_literals

from django.db import migrations, models
import microbot.validators


class Migration(migrations.Migration):

dependencies = [
('microbot', '0014_chatstate_context'),
]

operations = [
migrations.AlterField(
model_name='bot',
name='token',
field=models.CharField(db_index=True, help_text='Set token provided by Telegram API.', max_length=100, validators=[microbot.validators.validate_token], verbose_name='Token'),
),
migrations.AlterField(
model_name='handler',
name='pattern',
field=models.CharField(help_text='Regular expression the Handler will be triggered.', max_length=255, validators=[microbot.validators.validate_pattern], verbose_name='Pattern'),
),
migrations.AlterField(
model_name='headerparam',
name='value_template',
field=models.CharField(help_text='Set the value of the parameter. A jinja2 template.', max_length=255, validators=[microbot.validators.validate_template], verbose_name='Value template'),
),
migrations.AlterField(
model_name='request',
name='url_template',
field=models.CharField(help_text='Set the url to request. A jinja2 template.', max_length=255, validators=[microbot.validators.validate_template], verbose_name='Url template'),
),
migrations.AlterField(
model_name='response',
name='keyboard_template',
field=models.TextField(blank=True, help_text='Template to generate keyboard response. In jinja2.', null=True, validators=[microbot.validators.validate_template, microbot.validators.validate_telegram_keyboard], verbose_name='Keyboard template'),
),
migrations.AlterField(
model_name='response',
name='text_template',
field=models.TextField(help_text='Template to generate text response. In jinja2.', validators=[microbot.validators.validate_template, microbot.validators.validate_telegram_text_html], verbose_name='Text template'),
),
migrations.AlterField(
model_name='urlparam',
name='value_template',
field=models.CharField(help_text='Set the value of the parameter. A jinja2 template.', max_length=255, validators=[microbot.validators.validate_template], verbose_name='Value template'),
),
]
6 changes: 3 additions & 3 deletions microbot/models/bot.py
Expand Up @@ -15,10 +15,10 @@
from telegram.bot import InvalidToken
import ast
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.models import Q
from microbot import validators
import re

from django.core.exceptions import ValidationError

logger = logging.getLogger(__name__)

Expand All @@ -29,7 +29,7 @@ def validate_token(value):
@python_2_unicode_compatible
class Bot(MicrobotModel):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='bots', help_text=_("User who owns the bot."))
token = models.CharField(_('Token'), max_length=100, db_index=True, validators=[validate_token],
token = models.CharField(_('Token'), max_length=100, db_index=True, validators=[validators.validate_token],
help_text=_("Set token provided by Telegram API."))
user_api = models.OneToOneField(User, verbose_name=_("Bot User"), related_name='bot',
on_delete=models.CASCADE, blank=True, null=True,
Expand Down
10 changes: 7 additions & 3 deletions microbot/models/handler.py
Expand Up @@ -9,13 +9,15 @@
from django.conf.urls import url
import json
import logging
from microbot import validators

logger = logging.getLogger(__name__)


class AbstractParam(MicrobotModel):
key = models.CharField(_('Key'), max_length=255, help_text=_("Set the name of the parameter"))
value_template = models.CharField(_('Value template'), max_length=255, help_text=_("Set the value of the parameter. A jinja2 template."))
value_template = models.CharField(_('Value template'), max_length=255, validators=[validators.validate_template],
help_text=_("Set the value of the parameter. A jinja2 template."))

class Meta:
abstract = True
Expand All @@ -31,7 +33,8 @@ def process(self, **context):

@python_2_unicode_compatible
class Request(MicrobotModel):
url_template = models.CharField(_('Url template'), max_length=255, help_text=_("Set the url to request. A jinja2 template."))
url_template = models.CharField(_('Url template'), max_length=255, validators=[validators.validate_template],
help_text=_("Set the url to request. A jinja2 template."))
GET, POST, PUT, PATCH, DELETE = ("Get", "Post", "Put", "Patch", "Delete")
METHOD_CHOICES = (
(GET, _("Get")),
Expand Down Expand Up @@ -116,7 +119,8 @@ class Handler(MicrobotModel):
bot = models.ForeignKey(Bot, verbose_name=_('Bot'), related_name="handlers",
help_text=_("Bot which Handler is attached to."))
name = models.CharField(_('Name'), max_length=100, db_index=True, help_text=_("Set a name for the handler which helps you to remember it."))
pattern = models.CharField(_('Pattern'), max_length=255, help_text=_("Regular expression the Handler will be triggered."))
pattern = models.CharField(_('Pattern'), max_length=255, validators=[validators.validate_pattern],
help_text=_("Regular expression the Handler will be triggered."))
request = models.OneToOneField(Request, null=True, blank=True, help_text=_("Request the Handler processes."))
response = models.OneToOneField(Response, help_text=_("Set how Handler responses."))
enabled = models.BooleanField(_('Enable'), default=True, help_text=_("enable/disable Handler."))
Expand Down
5 changes: 4 additions & 1 deletion microbot/models/response.py
Expand Up @@ -5,14 +5,17 @@
import logging
from jinja2 import Template
from microbot.models.base import MicrobotModel
from microbot import validators

logger = logging.getLogger(__name__)

@python_2_unicode_compatible
class Response(MicrobotModel):
text_template = models.TextField(verbose_name=_("Text template"),
text_template = models.TextField(verbose_name=_("Text template"), validators=[validators.validate_template,
validators.validate_telegram_text_html],
help_text=_("Template to generate text response. In jinja2."))
keyboard_template = models.TextField(null=True, blank=True, verbose_name=_("Keyboard template"),
validators=[validators.validate_template, validators.validate_telegram_keyboard],
help_text=_("Template to generate keyboard response. In jinja2."))

class Meta:
Expand Down
6 changes: 4 additions & 2 deletions microbot/serializers/handler.py
@@ -1,6 +1,8 @@
from rest_framework import serializers
from microbot.models import Handler, Request, Response, UrlParam, HeaderParam, State
from microbot.serializers import StateSerializer, ResponseSerializer, ResponseUpdateSerializer
from microbot import validators


class AbsParamSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField()
Expand All @@ -19,7 +21,7 @@ class Meta:
fields = ('url_template', 'method', 'data', 'url_parameters', 'header_parameters')

class RequestUpdateSerializer(RequestSerializer):
url_template = serializers.CharField(required=False, max_length=255)
url_template = serializers.CharField(required=False, max_length=255, validators=[validators.validate_template])
method = serializers.ChoiceField(choices=Request.METHOD_CHOICES, required=False)


Expand Down Expand Up @@ -100,7 +102,7 @@ def update(self, instance, validated_data):

class HandlerUpdateSerializer(HandlerSerializer):
name = serializers.CharField(required=False, max_length=100)
pattern = serializers.CharField(required=False, max_length=250)
pattern = serializers.CharField(required=False, max_length=250, validators=[validators.validate_pattern])
priority = serializers.IntegerField(required=False, min_value=0)
response = ResponseUpdateSerializer(many=False, required=False)
request = RequestUpdateSerializer(many=False, required=False)
8 changes: 6 additions & 2 deletions microbot/serializers/response.py
@@ -1,11 +1,15 @@
from rest_framework import serializers
from microbot.models import Response
from microbot import validators


class ResponseSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Response
fields = ('text_template', 'keyboard_template')

class ResponseUpdateSerializer(ResponseSerializer):
text_template = serializers.CharField(required=False, max_length=1000)
keyboard_template = serializers.CharField(required=False, max_length=1000)
text_template = serializers.CharField(required=False, max_length=1000,
validators=[validators.validate_template, validators.validate_telegram_text_html])
keyboard_template = serializers.CharField(required=False, max_length=1000,
validators=[validators.validate_template, validators.validate_telegram_keyboard])
48 changes: 48 additions & 0 deletions microbot/validators.py
@@ -0,0 +1,48 @@
import re
from django.core.exceptions import ValidationError
from jinja2 import Template
from django.utils.translation import ugettext_lazy as _
import ast
from HTMLParser import HTMLParser

def validate_token(value):
if not re.match('[0-9]+:[-_a-zA-Z0-9]+', value):
raise ValidationError(_("%(value)s is not a valid token"), params={'value': value})

def validate_template(value):
try:
Template(value)
except:
raise ValidationError(_("Not correct jinja2 template: %(value)s"), params={'value': value})

def validate_pattern(value):
try:
re.compile(value)
except:
raise ValidationError(_("Not correct Regex: %(value)s"), params={'value': value})

def validate_telegram_keyboard(value):
try:
ast.literal_eval(value)
except:
raise ValidationError(_("Not correct keyboard: %(value)s. Check https://core.telegram.org/bots/api#replykeyboardmarkup"), params={'value': value})

def validate_telegram_text_html(value):
tags = ['b', 'i', 'a', 'code', 'pre']
found = []
msg = _("Not correct HTML for Telegram message. Check https://core.telegram.org/bots/api#html-style")

class TelegramHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
tags.index(tag)
found.append(tag)

def handle_endtag(self, tag):
found.pop(found.index(tag))
parser = TelegramHTMLParser()
try:
parser.feed(value)
if found:
raise ValidationError(msg)
except:
raise ValidationError(msg)
19 changes: 18 additions & 1 deletion tests/api/base.py
Expand Up @@ -51,7 +51,16 @@ def _test_post_list_ok(self, url, model, data):
content_type='application/json',
HTTP_AUTHORIZATION=self._gen_token(self.bot.owner.auth_token))
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
return response.json()
return response.json()

def _test_post_list_validation_error(self, url, model, data):
model.objects.all().delete()
response = self.client.post(url,
data=json.dumps(data),
content_type='application/json',
HTTP_AUTHORIZATION=self._gen_token(self.bot.owner.auth_token))
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
return response.json()

def _test_post_list_not_found_required_pre_created(self, url, model, data):
model.objects.all().delete()
Expand Down Expand Up @@ -92,6 +101,14 @@ def _test_put_detail_ok(self, url, data, view, *args):
force_authenticate(request, user=self.bot.owner)
response = view.as_view()(request, *args)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def _test_put_detail_validation_error(self, url, data, view, *args):
factory = APIRequestFactory()
request = factory.put(url, data, format="json")
force_authenticate(request, user=self.bot.owner)
response = view.as_view()(request, *args)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
return response

def _test_put_detail_not_auth(self, url, data, view, *args):
factory = APIRequestFactory()
Expand Down

0 comments on commit 66159a5

Please sign in to comment.