├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── dev.txt ├── pytest.ini ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── assets │ ├── linux.jpeg │ ├── linux.png │ ├── linux.tgz │ └── linux.zip ├── run_test.py ├── settings.py └── tests.py └── validator ├── __init__.py └── validators.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = validator/validators.py 3 | 4 | [report] 5 | include = validator/validators.py 6 | 7 | [html] 8 | directory = html -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyo 2 | *.pyc 3 | *.~ 4 | .idea 5 | *.log 6 | .coverage 7 | .venv 8 | venv 9 | coverage 10 | build 11 | dist 12 | htmlcov 13 | django_easy_validator.egg-info 14 | __pycache__ 15 | .pytest_cache 16 | html -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.6.5" 5 | 6 | test: 7 | adapter: sqlite3 8 | database: "validators" 9 | timeout: 500 10 | 11 | install: 12 | - pip install -e . 13 | - pip install django-coverage 14 | - pip install pytest-django 15 | - pip install django 16 | - pip install pytest-cov 17 | - pip install codecov 18 | 19 | script: 20 | - pytest --cov-report=html --cov-config=.coveragerc --cov=./ 21 | 22 | after_success: 23 | - codecov -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Younger Shen And His Friends 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the license file 2 | include LICENSE.txt 3 | 4 | # Include the readme file 5 | 6 | include README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Easy Validator 2 | this piece of software is inspired by the validation system of [laravel](https://laravel.com/docs/5.5/validation), 3 | you can use this software to validate your POST/GET data easily and softly. 4 | 5 | ![Travis](https://img.shields.io/travis/youngershen/django-easy-validator.svg) 6 | ![codecov](https://codecov.io/gh/youngershen/django-easy-validator/branch/master/graph/badge.svg) 7 | ![PyPI - License](https://img.shields.io/pypi/l/django-easy-validator.svg) 8 | ![PyPI](https://img.shields.io/pypi/v/django-easy-validator.svg) 9 | ![PyPI - Wheel](https://img.shields.io/pypi/wheel/django-easy-validator.svg) 10 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-easy-validator.svg) 11 | ![GitHub last commit](https://img.shields.io/github/last-commit/youngershen/django-easy-validator.svg) 12 | 13 | ## TODO 14 | 15 | * IPv4 pattern support 16 | * IPv6 pattern support 17 | * URL pattern support 18 | * 19 | 20 | ## Requirementse 21 | 22 | - Python 3.4 23 | - Python 3.5 24 | - Python 3.6 25 | - Python 3.7 26 | - Django 1.6 + 27 | 28 | ## Installation 29 | 30 | install from pypi : 31 | 32 | `pip install django-easy-validator` 33 | 34 | install from source code: 35 | 36 | `python setup.py install` 37 | 38 | ## Usage 39 | 40 | 1. create your own validator with Validator class. 41 | 42 | ```python 43 | from validator import Validator 44 | 45 | class LoginValidator(Validator): 46 | username = 'required|email' 47 | password = 'required' 48 | 49 | message = { 50 | 'username': { 51 | 'required': _('username is required'), 52 | 'email': _('username is not an email address') 53 | }, 54 | 'password': { 55 | 'required': _('password is required') 56 | } 57 | } 58 | 59 | ``` 60 | 61 | 2. spawn your validation class then call validate method to check it out. 62 | 63 | ```python 64 | 65 | validator = LoginValidator({'username': 'michael', 'password': '12345678'}) 66 | status = validator.validate() 67 | if status: 68 | print("login succeed") 69 | else: 70 | errors = validator.get_message() 71 | print(errors) 72 | ``` 73 | 74 | ## Supported Rules 75 | 76 | - [required](#required) 77 | - [accepted](#accepted) 78 | - [date](#data) 79 | - [date_before](#date_before) 80 | - [date_after](#date_after) 81 | - [date_range](#date_range) 82 | - [datetime](#datetime) 83 | - [datetime_before](#datetime_before) 84 | - [datetime_after](#datetime_after) 85 | - [datetime_range](#datetime_range) 86 | - [active_url](#active_url) 87 | - [numberic](#numberic) 88 | - [digits](#digits) 89 | - [regex](#regex) 90 | - [email](#email) 91 | - [min_length](#min_length) 92 | - [max_length](#max_length) 93 | - [ids](#ids) 94 | - [cellphone](#cellphone) 95 | - [alphabet](#alphabet) 96 | - [switch](#switch) 97 | - [unique](#unique) 98 | - [size](#size) 99 | - [min](#min) 100 | - [max](#max) 101 | - [file](#file) 102 | - [image](#image) 103 | - [video](#video) 104 | - [audio](#audio) 105 | - [attachement](#attachement) 106 | - [alpha_dash](#alpha_dash) 107 | - [alpha_number](#alpha_number) 108 | - [array](#array) 109 | - [date_before_equal](#date_before_equal) 110 | - [date_after_equal](#date_after_equal) 111 | - [datetime_before_equal](#datetime_before_equal) 112 | - [datetime_after_equal](#datetime_after_equal) 113 | - [between](#between) 114 | - [boolean](#boolean) 115 | - [username](#username) 116 | - [password](#password) 117 | - [ASCII](#ASCII) 118 | - [same](#same) 119 | - [decimal](#decimal) 120 | - [exist](#exist) 121 | - [unique_against](#unique_against) 122 | 123 | -------------------------------------------------------------- 124 | 125 | ### required 126 | 127 | ```python 128 | 129 | class LoginValidator(Validator): 130 | username = 'required' 131 | ``` 132 | 133 | the field with required rule cant be null or empty. 134 | 135 | ### accepted 136 | 137 | ```python 138 | class LoginValidator(Validator): 139 | username = 'required' 140 | remember_me = 'accepted' 141 | ``` 142 | 143 | the field with accepted must be one of the following strings, and the 144 | string case is insensitive. 145 | 146 | ```python 147 | ['yes', 'no', 'true', 'false', '0', '1'] 148 | ``` 149 | 150 | ### date 151 | 152 | ```python 153 | class RegisterValidator(Validator): 154 | birthday = 'required|date:%Y-%m-%d' 155 | ``` 156 | 157 | the field with date must be a python date format string, the parameter 158 | should be a format suit the datetime.datetime.strptime function. 159 | 160 | ### date_before 161 | 162 | ```python 163 | class SubmitValidator(Validator): 164 | due = 'required|date_before:2017-12-31' 165 | ``` 166 | 167 | the field with date_before must be a date format string suit xxxx-mm-dd. 168 | 169 | ### date_after 170 | 171 | ```python 172 | class SubmitValidator(Validator): 173 | date = 'required|date_after:2017-11-12' 174 | ``` 175 | the field with date_after must be a date format string suit xxxx-mm-dd. 176 | 177 | ### date_range 178 | 179 | ```python 180 | class SubmitValidator(Validator): 181 | date_range = 'required|date_range:2017-01-01,2017-12-31' 182 | ``` 183 | 184 | the field with date_range must be 2 date format strings and it should suit xxxx-mm-dd. 185 | 186 | 187 | ### datetime 188 | 189 | ```python 190 | class RegisterValidator(Validator): 191 | login_time = 'required|datetime:%Y-%m-%d %H:%M:%S' 192 | ``` 193 | 194 | the field with datetime and the parameter must be a datetime string suit the datetime.datetime.strptime function. 195 | 196 | ### datetime_before 197 | ```python 198 | class LoginValidator(Validator): 199 | time = 'required|datetime_before:2017-12-12 13:14:00' 200 | ``` 201 | 202 | the field with datetime_before must be a datetime string can strap to a datetime object and so as the parameter. 203 | 204 | 205 | ### datetime_after 206 | 207 | ```python 208 | class LoginValidator(Validator): 209 | time = 'required|datetime_after:2017-12-12 13:14:00' 210 | ``` 211 | 212 | the field with datetime_after must be a datetime string can strap to a datetime object and so as the parameter. 213 | 214 | 215 | ### datetime_range 216 | 217 | ```python 218 | class LoginValidator(Validator): 219 | period = 'datetime_range:2017-12-12 13:14:00, 2017-12-15 13:14:00' 220 | 221 | ``` 222 | 223 | ### active_url 224 | 225 | ```python 226 | class ActiveURLValidator(Validator): 227 | url = 'active_url' 228 | ``` 229 | 230 | the field with active_url must be a active url you could connect to and get reply. 231 | 232 | ### numberic 233 | 234 | ```python 235 | class RegisterValidator(Validator): 236 | id_number = 'required|numberic' 237 | ``` 238 | 239 | the field with numberic must be a number, it should be a integer not a float or something. 240 | 241 | ### digits 242 | 243 | ```python 244 | class RegisterValidator(Validator): 245 | product_series = 'required|digits' 246 | ``` 247 | 248 | the field with digits must only contains digits token. 249 | 250 | ### regex 251 | 252 | ```python 253 | class RegisterValidator(Validator): 254 | id_number = 'required|regex:^0[0-9]{5,10}$' 255 | ``` 256 | 257 | the field with regex must be a string, the parameter could be any regex pattern string, this 258 | rule will match the field value with the paramter pattern out. 259 | 260 | ### email 261 | 262 | ```python 263 | class RegisterValidator(Validator): 264 | username = 'required|email' 265 | 266 | ``` 267 | 268 | the field with email must be a valid email address string. 269 | 270 | 271 | ### min_length 272 | 273 | ```python 274 | 275 | class Register(Validator): 276 | password = 'min_length:8' 277 | 278 | ``` 279 | 280 | the field with **min_length** must has the minimal length of string or value of number. 281 | 282 | ### max_length 283 | 284 | ```python 285 | 286 | class RegisterValidator(Validator): 287 | username = 'max_length:20' 288 | 289 | ``` 290 | 291 | the field with **min_length** must has the minimal length of string or value of number. 292 | 293 | 294 | ### ids 295 | 296 | ```python 297 | 298 | class DeleteValidator(Validator): 299 | ids = 'ids' 300 | ``` 301 | 302 | the field with ids must be a integer string splited by the comma, such as '1,2,3,4,5' 303 | 304 | 305 | ### cellphone 306 | 307 | ```python 308 | 309 | class RegisterValidator(Validator): 310 | phone = 'cellphone' 311 | 312 | ``` 313 | 314 | the field with cellphone rule must be a real cellphone number , it could be '+8613811754531' or just '13811754531' 315 | 316 | 317 | ### alphabet 318 | 319 | ```python 320 | 321 | class RegisterValidator(Validator): 322 | name = 'alphabet' 323 | 324 | ``` 325 | 326 | the field with alphabet must be a standard alphabet string. 327 | 328 | 329 | ### switch 330 | 331 | ```python 332 | 333 | class LoginValidator(Validator): 334 | rememberme ='switch:yes,no' 335 | 336 | ``` 337 | 338 | the field with switch must be in the params array, it this case , rememberme must be yes or no. 339 | 340 | 341 | ### unique 342 | 343 | ```python 344 | 345 | class RegisterValidator(Validator): 346 | username = 'unique:AUTH_USER_MODEL,username' 347 | email = 'unique:account.User,email' 348 | 349 | ``` 350 | 351 | the field with unique must has 2 parameters , the first is appname.modelname, the second is the field to check, 352 | actually it is also the column to check if is already exists in this table, if you want to validate the django auth 353 | user, the first paramater must be AUTH_USER_MODEL. 354 | 355 | 356 | ### size 357 | ```python 358 | 359 | class UpdateProfileValidator(Validator) 360 | avatar = 'image|size:2048' 361 | 362 | ``` 363 | 364 | the field with size has 4 kind of types , if the given field is an file, the parameter means the size of the file with KB, 365 | if the field is a string , the parameter means the size is the string length, if the field is an integer , the size means 366 | the integer value, if the field is an array, the size means the array size. 367 | 368 | 369 | ### min 370 | 371 | ```python 372 | 373 | class UpdateProfileValidator(Validator): 374 | profile = 'image|min:2048' 375 | 376 | ``` 377 | 378 | the field with min has the same meaning of size, it's just check the minimal of the field , the size is check the equal of the field. 379 | 380 | ### max 381 | 382 | ```python 383 | 384 | class UpdateProfileValidator(Validator): 385 | profile = 'image|min:2048' 386 | 387 | ``` 388 | 389 | the field with min has the same meaning of size, it's just check the maximal of the field , the size is check the equal of the field. 390 | 391 | 392 | ### file 393 | 394 | ```python 395 | 396 | class UploadValidator(Validator): 397 | file = 'file:png,jpeg,zip,rar' 398 | 399 | ``` 400 | 401 | the field with file rule is to check the file type. parameters needed to be a string array. 402 | 403 | 404 | ### image 405 | 406 | ```python 407 | 408 | class UploadValidator(Validator): 409 | file = 'image' 410 | 411 | ``` 412 | 413 | the field with file image rule just do the same thing like file , it is a convenient way to check the common image type , in this way you do not need to add image ext parameter. 414 | the check type is ['png', 'jpeg', 'gif', 'jpg', 'svg'] . 415 | 416 | 417 | ### video 418 | 419 | ```python 420 | 421 | class UploadValidator(Validator): 422 | file = 'video' 423 | 424 | ``` 425 | 426 | the field with video rule just do the same thing like file , it is a convenient way to check the common video type , in this way you do not need to add video ext parameter. 427 | the check type is ['mp4', 'avi', 'mkv', 'flv', 'rmvb']. 428 | 429 | 430 | ### audio 431 | 432 | ```python 433 | 434 | class UploadValidator(Validator): 435 | file = 'audio' 436 | 437 | ``` 438 | 439 | the field with audio rule just do the same thing like file , it is a convenient way to check the common audio type , in this way you do not need to add video ext parameter. 440 | the check type is ['mp3', 'wma', 'flac', 'ape', 'ogg']. 441 | 442 | 443 | ### attachement 444 | 445 | ```python 446 | 447 | class UploadValidator(Validator): 448 | file = 'attachement' 449 | ``` 450 | 451 | the field with attachement rule just do the same thing like file , it is a convenient way to check the common attachement type , in this way you do not need to add video ext parameter. 452 | the check type is ['doc', 'zip', 'ppt', 'docx', 'excel', 'rar']. 453 | 454 | ### alpha_dash 455 | 456 | ```python 457 | class RegisterValidator(Validator): 458 | username = 'alpha_dash' 459 | 460 | ``` 461 | 462 | the field with alpha_dash rule is just check out if the string only includes alphabet characters and dash character. 463 | 464 | 465 | ### alpha_number 466 | 467 | ```python 468 | class RegisterValidator(Validator): 469 | username = 'alpha_number' 470 | 471 | ``` 472 | the field with alpha_number the given value must conbines with only alphabets and numbers. 473 | 474 | 475 | ### array 476 | 477 | ```python 478 | class RegisterValidator(Validator): 479 | hobbies = 'array' 480 | 481 | ``` 482 | 483 | the field with array must be a array string ,such as 'guitar, computer, music, food'. 484 | 485 | ### date_before_equal 486 | 487 | ```python 488 | class RegisterValidator(Validator): 489 | due_at = 'date_before_equal:2018-01-08' 490 | ``` 491 | 492 | the field with date_before_equal just check the given value must be a date string and before or equal the given parameter. 493 | 494 | ### date_after_equal 495 | 496 | ```python 497 | class RegisterValidator(Validator): 498 | due_at = 'date_after_equal:2018-01-08' 499 | ``` 500 | 501 | the field with date_after_equal just check the given value must be a date string and afer or equal the given parameter. 502 | 503 | ### datetime_before_equal 504 | 505 | ```python 506 | class RegisterValidator(Validator): 507 | due_at='datetime_before_equal:1990-12-12 06:08:26' 508 | ``` 509 | 510 | the field with datetime_before_equal just check the given value must be a datetime string and befor the given parameter. 511 | 512 | ### datetime_after_equal 513 | 514 | ```python 515 | class RegisterValidator(Validator): 516 | due_at='datetime_after_equal:1990-12-12 06:08:26' 517 | ``` 518 | 519 | the field with datetime_after_equal just check the given value must be a datetime string and after the given parameter. 520 | 521 | 522 | ### between 523 | 524 | ```python 525 | class RegisterValidator(Validator): 526 | age = 'between:10, 15' 527 | ``` 528 | 529 | the field with between requires the given field value must be a integer number and it's value must between the parameters. 530 | 531 | 532 | ### boolean 533 | 534 | ```python 535 | class RegisterValidator(Valiadtor): 536 | remember = 'boolean' 537 | 538 | ``` 539 | 540 | the field with boolean requires the given value should be one of this '['0', '1', 'true', 'false']' 541 | 542 | ### username 543 | 544 | ```python 545 | class RegisterValidator(Validator): 546 | username = 'username' 547 | ``` 548 | 549 | the field with username requires the given value starts with an alphabet character and it could includes with numbers , dash, underscore. 550 | 551 | ### password 552 | ```python 553 | class RegisterValidator(Validator): 554 | password = 'password:low' 555 | 556 | ``` 557 | 558 | the field with password ruls requires an parameter , it could be : low, middle , high. 559 | the 3 different check methods has different check level. 560 | 561 | the low method means the password length must longer than 7 562 | 563 | the middle method means the password length must longer than 7 and it sould contains lower , upper latin characters and digits 564 | 565 | the high method means the password lenght must longer than 7 and it sould contains lower , upper latin characters and digits, and special 566 | 567 | characters. 568 | 569 | ### ASCII 570 | ```python 571 | class ASCIIValidator(Validator): 572 | ascii = 'ascii' 573 | 574 | ``` 575 | 576 | the ascii requires the given value only includes ascii characters. 577 | 578 | 579 | ### same 580 | 581 | ```python 582 | class SameValidator(Validator): 583 | password = 'required|min_lengh:8' 584 | password_confirm = 'same:password' 585 | 586 | ``` 587 | 588 | the same rule just validate the give field vale checks the value if is same as the other value. 589 | 590 | ### deciaml 591 | ```python 592 | 593 | class DecimalValidator(Validator): 594 | price = 'required|decimal' 595 | 596 | ``` 597 | 598 | the decimal rule just validate the give field value if is a decimal or a float number or number string. 599 | 600 | ### exist 601 | ```python 602 | class ExistValidator(Validator): 603 | username = 'required|exist:AUTH_USER_MODEL,username' 604 | ``` 605 | 606 | the exist rule just check the given field record weather exists in the database table. 607 | 608 | ### unique_against 609 | ```python 610 | class UniqueAgainstValidator(Validator): 611 | username = 'required|unique_against:AUTH_USER_MODEL, username, youngershen' 612 | ``` 613 | the unique_against validator check weather the given value column exists in the database and against the third parameter. 614 | 615 | ### pascii 616 | 617 | ```python 618 | class PASCIIValidator(Validator): 619 | username = 'required|pascii' 620 | ``` 621 | pascii ensure the input string must be a ascii string and it can't contians the unprintable 622 | ascii characters. 623 | 624 | ### unblank 625 | 626 | unblank make the given field must be a none-blank string, it could be empty, but if it is not empyt, it should be a unblank 627 | string. 628 | 629 | the rule indicates the given field shoud be empty string or it should be a none-empty string, like '\r\n\r\n' it will return a 630 | False, becaus '\r\n' is A **empty** string, the '' wille return True, because it is a normal empty string. 631 | 632 | ### integer 633 | 634 | this rule check the give value if it's a proper decimal integer 635 | 636 | ### pos_integer 637 | 638 | the positive value version the integer value 639 | 640 | ### neg_integer 641 | 642 | the negative value version the integer value 643 | 644 | ### percentage 645 | 646 | check the give value if it's a integer value from 0 to 100 647 | 648 | ### ip_address 649 | 650 | just check the given value if it's a ip address, both checks for ip v4 and v6 address. 651 | 652 | ## Advanced Topic 653 | 654 | ### Custom Validation Rules 655 | 656 | ```python 657 | # define the rule 658 | from validator import BaseRule 659 | class TestRule(BaseRule): 660 | name = 'test_rule' 661 | message = 'test custom rule failed' 662 | description = 'just for custom rule test' 663 | 664 | def check_value(self): 665 | self.status = True if self.field_value == 'test' else False 666 | 667 | def check_null(self): 668 | pass 669 | 670 | 671 | # define a validator to use the rule 672 | class TestRuleValidator(Validator): 673 | name = 'test_rule' 674 | 675 | # to run the validation 676 | extra_rules = { TestRule.get_name(): TestRule } 677 | validator = TestRuleValidator(extra_rules=extra_rules, data={'name': 'test'}) 678 | assert validator.validate() 679 | 680 | ``` 681 | 682 | custom a validation rule is very easy, you just import the BaseRule and implements the method , 683 | the most important thing is before you use your rule , you should pass it to your validator 684 | class when it init through the extra_rules parameter. 685 | 686 | 687 | ## development 688 | 689 | 1. clone the project 690 | 2. pip install dev.txt 691 | 3. fix some bugs 692 | 4. add test case in tests/tests.py 693 | 5. run pytest command in terminal and make sure your code is ok 694 | 6. push and make a pull request 695 | 7. or you can run python setup.py bdist_wheel / sdist to upload the package 696 | 8. then just run twine uplaod ./dist/* to upload package to pypi -------------------------------------------------------------------------------- /dev.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.3.4 2 | atomicwrites==1.4.0 3 | attrs==21.2.0 4 | bleach==3.3.0 5 | certifi==2020.12.5 6 | chardet==4.0.0 7 | codecov==2.1.11 8 | colorama==0.4.4 9 | coverage==5.5 10 | Django==3.2.2 11 | django-coverage==1.2.4 12 | -e git+git@github.com:youngershen/django-easy-validator.git@26a8b65b777f359f9aab99dec58e0a3687bf6ed5#egg=django_easy_validator 13 | docutils==0.17.1 14 | idna==2.10 15 | importlib-metadata==4.0.1 16 | iniconfig==1.1.1 17 | keyring==23.0.1 18 | packaging==20.9 19 | pkginfo==1.7.0 20 | pluggy==0.13.1 21 | py==1.10.0 22 | Pygments==2.9.0 23 | pyparsing==2.4.7 24 | pytest==6.2.4 25 | pytest-cov==2.11.1 26 | pytest-django==4.2.0 27 | pytz==2021.1 28 | pywin32-ctypes==0.2.0 29 | readme-renderer==29.0 30 | requests==2.25.1 31 | requests-toolbelt==0.9.1 32 | rfc3986==1.5.0 33 | six==1.16.0 34 | sqlparse==0.4.1 35 | toml==0.10.2 36 | tqdm==4.60.0 37 | twine==3.4.1 38 | urllib3==1.26.4 39 | webencodings==0.5.1 40 | zipp==3.4.1 41 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE=tests.settings 3 | python_files = tests.py 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=0 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # PROJECT : django-easy-validator # TIME : 18-1-2 上午9:44 # AUTHOR : Younger Shen # EMAIL : younger.x.shen@gmail.com # CELL : 13811754531 # WECHAT : 13811754531 # https://github.com/youngershen/ import os from setuptools import setup, find_packages def read(fname): with open(os.path.join(os.path.dirname(__file__), fname)) as f: return f.read() setup( name='django-easy-validator', version='1.7.0', description='a very easy use django request POST/GET data validator', long_description=read('README.md'), long_description_content_type="text/markdown", url='https://github.com/youngershen/django-easy-validator', # Author details author='Younger Shen', author_email='shenyangang@163.com', author_qq='89198011', author_wechat='13811754531', author_cell='13811754531', # Choose your license license='MIT', # See https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[ # How mature is this project? Common values are # 3 - Alpha # 4 - Beta # 5 - Production/Stable 'Development Status :: 5 - Production/Stable', # Indicate who your project is intended for 'Intended Audience :: Developers', # Pick your license as you wish (should match "license" above) 'License :: OSI Approved :: MIT License', # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Framework :: Django :: 1.6', 'Framework :: Django :: 1.7', 'Framework :: Django :: 1.8', 'Framework :: Django :: 1.9', 'Framework :: Django :: 2.0', ], # What does your project relate to? keywords='a django request POST/GET data validator', # You can just specify the packages manually here if your project is # simple. Or you can use find_packages(). packages=find_packages(), install_requires=[ 'Django', ], python_requires='>=3.6', ) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # PROJECT : django-easy-validator 2 | # TIME : 18-1-2 上午9:44 3 | # AUTHOR : Younger Shen 4 | # EMAIL : younger.x.shen@gmail.com 5 | # CELL : 13811754531 6 | # WECHAT : 13811754531 7 | # https://github.com/youngershen/ 8 | 9 | -------------------------------------------------------------------------------- /tests/assets/linux.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngershen/django-easy-validator/ebc630025fcf2a54dcd86a31839f89a3f481a1a8/tests/assets/linux.jpeg -------------------------------------------------------------------------------- /tests/assets/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngershen/django-easy-validator/ebc630025fcf2a54dcd86a31839f89a3f481a1a8/tests/assets/linux.png -------------------------------------------------------------------------------- /tests/assets/linux.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngershen/django-easy-validator/ebc630025fcf2a54dcd86a31839f89a3f481a1a8/tests/assets/linux.tgz -------------------------------------------------------------------------------- /tests/assets/linux.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngershen/django-easy-validator/ebc630025fcf2a54dcd86a31839f89a3f481a1a8/tests/assets/linux.zip -------------------------------------------------------------------------------- /tests/run_test.py: -------------------------------------------------------------------------------- 1 | # Project: django-easy-validator 2 | # File : simple.py 3 | # Date : 2021/5/11 10:05 4 | # Author: Younger Shen <申延刚> 5 | # Web: https://github.com/youngershen 6 | # Cell: 13811754531 7 | # Email : shenyangang@163.com 8 | from validator import Validator 9 | 10 | 11 | class PrintableASCIIValidator(Validator): 12 | username = 'pascii:true' 13 | 14 | 15 | if __name__ == '__main__': 16 | data = { 17 | 'username': ' @ ' 18 | } 19 | 20 | v = PrintableASCIIValidator(data) 21 | 22 | print(v.validate()) 23 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | # PROJECT : django-easy-validator 2 | # TIME : 18-1-2 上午9:44 3 | # AUTHOR : Younger Shen 4 | # EMAIL : younger.x.shen@gmail.com 5 | # CELL : 13811754531 6 | # WECHAT : 13811754531 7 | # https://github.com/youngershen/ 8 | 9 | SECRET_KEY = "@1o4_s8+lfapx2%c7azo6orns9p-o#9(b$96mkf#+3+kt1(gl_" 10 | 11 | INSTALLED_APPS = [ 12 | 'django.contrib.admin', 13 | 'django.contrib.auth', 14 | 'django.contrib.contenttypes', 15 | 'validator', 16 | 'pytest_django' 17 | ] 18 | 19 | DATABASES = { 20 | 'default': { 21 | 'ENGINE': 'django.db.backends.sqlite3', 22 | 'NAME': 'validators', 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | # PROJECT : django-easy-validator 2 | # TIME : 18-1-2 上午9:44 3 | # AUTHOR : Younger Shen 4 | # EMAIL : younger.x.shen@gmail.com 5 | # CELL : 13811754531 6 | # WECHAT : 13811754531 7 | # https://github.com/youngershen/ 8 | 9 | from io import BytesIO 10 | from django.test import TestCase 11 | from django.core.files.uploadedfile import InMemoryUploadedFile 12 | from validator import Validator, BaseRule 13 | 14 | 15 | class AlphaNumber(Validator): 16 | code = 'alpha_number' 17 | 18 | message = { 19 | 'code': { 20 | 'alpha_number': '{VALUE} is not a alpha number type series.' 21 | } 22 | } 23 | 24 | 25 | class Array(Validator): 26 | ids = 'array' 27 | message = { 28 | 'ids': { 29 | 'array': '{VALUE} is not a array type series.' 30 | } 31 | } 32 | 33 | 34 | class Between(Validator): 35 | age = 'between:10,20' 36 | message = { 37 | 'age': { 38 | 'between': '{VALUE} is not between 10 to 20' 39 | } 40 | } 41 | 42 | 43 | class Boolean(Validator): 44 | remember_me = 'boolean' 45 | message = { 46 | 'remember_me': { 47 | 'boolean': '{VALUE} is not a boolean type value.' 48 | } 49 | } 50 | 51 | 52 | class TestRule(BaseRule): 53 | name = 'test_rule' 54 | message = 'test custom rule failed' 55 | description = 'just for custom rule test' 56 | 57 | def check_value(self): 58 | self.status = True if self.field_value == 'test' else False 59 | 60 | def check_null(self): 61 | pass 62 | 63 | 64 | class TestRuleValidator(Validator): 65 | name = 'test_rule' 66 | 67 | 68 | class Required(Validator): 69 | username = 'required' 70 | 71 | message = { 72 | 'username': { 73 | 'required': 'username is required' 74 | } 75 | } 76 | 77 | 78 | class Accepted(Validator): 79 | remember = 'accepted' 80 | 81 | message = { 82 | 'remember': { 83 | 'accepted': 'input of {VALUE} is not accepted in {FLAGS}' 84 | } 85 | } 86 | 87 | 88 | class AcceptedCustom(Validator): 89 | remember = 'accepted:shi,fou' 90 | message = { 91 | 'remember': { 92 | 'accepted': 'you just input {VALUE}' 93 | } 94 | } 95 | 96 | 97 | class Date(Validator): 98 | birthday = 'date' 99 | message = { 100 | 'birthday': { 101 | 'date': 'date format is invalid' 102 | } 103 | } 104 | 105 | 106 | class DateCustom(Validator): 107 | birthday = 'date:%Y' 108 | message = { 109 | 'birthday': { 110 | 'date': 'date format is not ok' 111 | } 112 | } 113 | 114 | 115 | class DateBefore(Validator): 116 | expired_at = 'date_before:1990-12-12' 117 | message = { 118 | 'expired_at': { 119 | 'date_before': 'date is not before 1990-12-12' 120 | } 121 | } 122 | 123 | 124 | class DateBeforeCustom(Validator): 125 | expired_at = 'date_before:1990,%Y,%Y' 126 | message = { 127 | 'expired_at': { 128 | 'date_before': 'date is not before 1990' 129 | } 130 | } 131 | 132 | 133 | class DateAfter(Validator): 134 | due_at = 'date_after:1990-12-12' 135 | message = { 136 | 'due_at': { 137 | 'date_after': 'date is not after 1990-12-12' 138 | } 139 | } 140 | 141 | 142 | class DateAfterCustom(Validator): 143 | due_at = 'date_after:1990,%Y,%Y' 144 | message = { 145 | 'due_at': { 146 | 'date_after': 'date is not after 1990' 147 | } 148 | } 149 | 150 | 151 | class DateRange(Validator): 152 | period = 'date_range:1990-12-12, 1991-12-12' 153 | message = { 154 | 'period': { 155 | 'date_range': 'date is not in range of {BEGIN} to {END}' 156 | } 157 | } 158 | 159 | 160 | class Datetime(Validator): 161 | now = 'datetime' 162 | message = { 163 | 'now': { 164 | 'datetime': 'it is not a datetime format string' 165 | } 166 | } 167 | 168 | 169 | class DatetimeBefore(Validator): 170 | due_at = 'datetime_before:1990-12-12 15:31:10' 171 | message = { 172 | 'due_at': { 173 | 'datetime_before': 'the input is not before {DATETIME}' 174 | } 175 | } 176 | 177 | 178 | class DatetimeAfter(Validator): 179 | after_at = 'datetime_after:1990-12-12 15:31:10' 180 | message = { 181 | 'after_at': { 182 | 'datetime_after': 'the input is not after {DATETIME}' 183 | } 184 | } 185 | 186 | 187 | class DatetimeRange(Validator): 188 | range_at = 'datetime_range:1990-12-12 15:31:10,1991-12-12 15:31:10' 189 | message = { 190 | 'range_at': { 191 | 'datetime_range': 'the input is not after {BEGIN} to {END}' 192 | } 193 | } 194 | 195 | 196 | class ActiveUrl(Validator): 197 | url = 'active_url' 198 | message = { 199 | 'url': { 200 | 'active_url': 'it is not a active url' 201 | } 202 | } 203 | 204 | 205 | class Numberic(Validator): 206 | number = 'numberic' 207 | message = { 208 | 'number': { 209 | 'numberic': '{VALUE} of number is not numberic' 210 | } 211 | } 212 | 213 | 214 | class Digits(Validator): 215 | card = 'digits' 216 | message = { 217 | 'card': { 218 | 'digits': '{VALUE} of card is not digits' 219 | } 220 | } 221 | 222 | 223 | class Regex(Validator): 224 | parse_args = False 225 | identity = 'regex:[0-9a-z]{3,5}' 226 | message = { 227 | 'identity': { 228 | 'regex': '{VALUE} of identity is not match the pattern {REGEX}' 229 | } 230 | } 231 | 232 | 233 | class Email(Validator): 234 | email = 'email' 235 | message = { 236 | 'email': { 237 | 'email': '{VALUE} is not an email address' 238 | } 239 | } 240 | 241 | 242 | class MinLength(Validator): 243 | username = 'min_length:4' 244 | message = { 245 | 'username': { 246 | 'min_length': '{VALUE} of username is shotter than 4' 247 | } 248 | } 249 | 250 | 251 | class MaxLength(Validator): 252 | username = 'max_length:7' 253 | message = { 254 | 'username': { 255 | 'max_length': '{VALUE} of username is longger than 7' 256 | } 257 | } 258 | 259 | 260 | class IDS(Validator): 261 | ids = 'ids' 262 | message = { 263 | 'ids': { 264 | 'ids': '{VALUE} of ids is not a id series' 265 | } 266 | } 267 | 268 | 269 | class Cellphone(Validator): 270 | cellphone = 'cellphone' 271 | message = { 272 | 'cellphone': { 273 | 'cellphone': '{VALUE} is not a cellphone number' 274 | } 275 | } 276 | 277 | 278 | class Alphabet(Validator): 279 | alphabet = 'alphabet' 280 | message = { 281 | 'alphabet': { 282 | 'alphabet': '{VALUE} of alphabet is not alphabet' 283 | } 284 | } 285 | 286 | 287 | class Switch(Validator): 288 | accepted = 'switch:ok,good,awesome' 289 | message = { 290 | 'accepted': { 291 | 'switch': '{VALUE} of accepted is not in [{SWITCH}]' 292 | } 293 | } 294 | 295 | 296 | class Unique(Validator): 297 | user_id = 'unique:AUTH_USER_MODEL,id' 298 | message = { 299 | 'user_id': { 300 | 'unique': '{VALUE} of {MODEL} with id is not unique' 301 | } 302 | } 303 | 304 | 305 | class Size(Validator): 306 | username = 'size:string,5' 307 | number = 'size:number,5' 308 | profile = 'size:array,2' 309 | avatar = 'size:file,13.903' 310 | 311 | message = { 312 | 'username': { 313 | 'size': 'size of username is not equals to 5' 314 | }, 315 | 'number': { 316 | 'size': 'size of number is not equals to 5' 317 | }, 318 | 'profile': { 319 | 'size': 'size of profile is not equals to 2' 320 | }, 321 | 'avatar': { 322 | 'size': 'size of avatar is not equals to 13.903KB' 323 | } 324 | } 325 | 326 | 327 | class Min(Validator): 328 | age = 'min:number,15' 329 | 330 | message = { 331 | 'age': { 332 | 'min': 'sorry we do not support service to people who is under 15.' 333 | } 334 | } 335 | 336 | 337 | class Max(Validator): 338 | age = 'max:number,50' 339 | message = { 340 | 'age': { 341 | 'max': 'sorry we do not support service to people who is older than 50.' 342 | } 343 | } 344 | 345 | 346 | class File(Validator): 347 | file = 'file:png,jpeg,zip,rar' 348 | 349 | message = { 350 | 'file': { 351 | 'file': 'file is not allowed to upload' 352 | } 353 | } 354 | 355 | 356 | class AlphaDash(Validator): 357 | username = 'alpha_dash' 358 | 359 | message = { 360 | 'username': { 361 | 'alpha_dash': 'username should only includes alphabet and dash characters.' 362 | } 363 | } 364 | 365 | 366 | class Username(Validator): 367 | username = 'username' 368 | message = { 369 | 'username': { 370 | 'username': 'the input {VALUE} is not a proper username.' 371 | } 372 | } 373 | 374 | 375 | class PasswordLow(Validator): 376 | password = 'password:low' 377 | message = { 378 | 'password': { 379 | 'password': 'the input is not a proper password.' 380 | } 381 | } 382 | 383 | 384 | class PasswordMiddle(Validator): 385 | password = 'password:middle' 386 | message = { 387 | 'password': { 388 | 'password': 'the input is not a proper password.' 389 | } 390 | } 391 | 392 | 393 | class PasswordHigh(Validator): 394 | password = 'password:high' 395 | message = { 396 | 'password': { 397 | 'password': 'the input is not a proper password.' 398 | } 399 | } 400 | 401 | 402 | class ASCII(Validator): 403 | seq = 'ascii' 404 | 405 | 406 | class Same(Validator): 407 | password = 'required' 408 | password_confirm = 'required|same:password' 409 | 410 | 411 | class Decimal(Validator): 412 | price = 'required|decimal' 413 | 414 | 415 | class Exist(Validator): 416 | uid = 'required|exist:AUTH_USER_MODEL,id' 417 | 418 | 419 | class UniqueAgainst(Validator): 420 | username = 'required|unique_against:AUTH_USER_MODEL, username, youngershen' 421 | 422 | 423 | class PrintableASCII(Validator): 424 | username = 'pascii' 425 | 426 | message = { 427 | 'username': { 428 | 'pascii': '用户名不能为空' 429 | } 430 | } 431 | 432 | 433 | class PrintableASCIINoBlank(Validator): 434 | username = 'pascii:true' 435 | message = { 436 | 'username': { 437 | 'pascii': '用户名不能为空' 438 | } 439 | } 440 | 441 | 442 | class Unblank(Validator): 443 | msg = 'unblank' 444 | message = { 445 | 'msg': { 446 | 'unblank': 'msg is not be able to be blank' 447 | } 448 | } 449 | 450 | 451 | class Integer(Validator): 452 | age = 'integer' 453 | 454 | message = { 455 | 'age': { 456 | 'integer': 'this it not a integer' 457 | } 458 | } 459 | 460 | 461 | class PosInteger(Validator): 462 | age = 'pos_integer' 463 | message = { 464 | 'age': { 465 | 'pos_integer': 'this it not a pos integer' 466 | } 467 | } 468 | 469 | 470 | class NegInteger(Validator): 471 | neg = 'neg_integer' 472 | message = { 473 | 'neg': { 474 | 'neg_integer': 'this is not a neg integer' 475 | } 476 | } 477 | 478 | 479 | class Percentage(Validator): 480 | discount = 'percentage' 481 | message = { 482 | 'discount': { 483 | 'percentage': 'this is not a precentage value' 484 | } 485 | } 486 | 487 | 488 | class IPAddress(Validator): 489 | ip = 'ip_address' 490 | 491 | message = { 492 | 'ip': { 493 | 'ip_address': 'this is not an ip address' 494 | } 495 | } 496 | 497 | # ====================================================================================================================== 498 | 499 | 500 | class IPAddressTestCase(TestCase): 501 | def setUp(self) -> None: 502 | self.validator = IPAddress 503 | self.valid_data = { 504 | 'ip': '127.0.0.1' 505 | } 506 | self.valid_data2 = { 507 | 'ip': '2001:0db8:85a3:0000:0000:8a2e:0370:7334' 508 | } 509 | 510 | self.invalid_data = { 511 | 'ip': '-10' 512 | } 513 | 514 | def test_valid(self): 515 | validator = self.validator(self.valid_data) 516 | validator.validate() 517 | self.assertTrue(validator.validate()) 518 | 519 | validator = self.validator(self.valid_data2) 520 | validator.validate() 521 | self.assertTrue(validator.validate()) 522 | 523 | def test_invalid(self): 524 | validator = self.validator(self.invalid_data) 525 | validator.validate() 526 | self.assertFalse(validator.validate()) 527 | 528 | 529 | class PercentageTestCase(TestCase): 530 | def setUp(self) -> None: 531 | self.validator = Percentage 532 | self.valid_data = { 533 | 'discount': '10' 534 | } 535 | self.invalid_data = { 536 | 'discount': '-10' 537 | } 538 | 539 | def test_valid(self): 540 | validator = self.validator(self.valid_data) 541 | validator.validate() 542 | self.assertTrue(validator.validate()) 543 | 544 | def test_invalid(self): 545 | validator = self.validator(self.invalid_data) 546 | validator.validate() 547 | self.assertFalse(validator.validate()) 548 | 549 | 550 | class NegIntegerTestCase(TestCase): 551 | def setUp(self) -> None: 552 | self.validator = NegInteger 553 | self.valid_data = { 554 | 'neg': '-1' 555 | } 556 | self.valid_data2 = { 557 | 'neg': -2 558 | } 559 | self.invalid_data = { 560 | 'neg': '2' 561 | } 562 | self.invalid_data2 = { 563 | 'neg': 2 564 | } 565 | 566 | def test_valid(self): 567 | validator = self.validator(self.valid_data) 568 | validator.validate() 569 | self.assertTrue(validator.validate()) 570 | 571 | validator = self.validator(self.valid_data2) 572 | validator.validate() 573 | self.assertTrue(validator.validate()) 574 | 575 | def test_invalid(self): 576 | validator = self.validator(self.invalid_data) 577 | validator.validate() 578 | self.assertFalse(validator.validate()) 579 | 580 | validator = self.validator(self.invalid_data2) 581 | validator.validate() 582 | self.assertFalse(validator.validate()) 583 | 584 | 585 | class PosIntegerTestCase(TestCase): 586 | def setUp(self) -> None: 587 | self.validator = PosInteger 588 | self.valid_data = { 589 | 'age': 32 590 | } 591 | self.valid_data2 = { 592 | 'age': '+32' 593 | } 594 | 595 | self.invalid_data = { 596 | 'age': '-32' 597 | } 598 | self.invalid_data2 = { 599 | 'age': '-1' 600 | } 601 | 602 | def test_valid(self): 603 | validator = self.validator(self.valid_data) 604 | validator.validate() 605 | self.assertTrue(validator.validate()) 606 | 607 | validator = self.validator(self.valid_data2) 608 | validator.validate() 609 | self.assertTrue(validator.validate()) 610 | 611 | def test_invalid(self): 612 | validator = self.validator(self.invalid_data) 613 | validator.validate() 614 | self.assertFalse(validator.validate()) 615 | 616 | validator = self.validator(self.invalid_data2) 617 | validator.validate() 618 | self.assertFalse(validator.validate()) 619 | 620 | 621 | class IntegerTestCase(TestCase): 622 | def setUp(self): 623 | self.validator = Integer 624 | self.valid_data = { 625 | 'age': '10' 626 | } 627 | self.valid_data2 = { 628 | 'age': '' 629 | } 630 | self.invalid_data = { 631 | 'age': 'aa' 632 | } 633 | self.invalid_data2 = { 634 | 'age': '-b' 635 | } 636 | 637 | def test_valid(self): 638 | validator = self.validator(self.valid_data) 639 | validator.validate() 640 | self.assertTrue(validator.validate()) 641 | 642 | validator = self.validator(self.valid_data2) 643 | validator.validate() 644 | self.assertTrue(validator.validate()) 645 | 646 | def test_invalid(self): 647 | validator = self.validator(self.invalid_data) 648 | validator.validate() 649 | self.assertFalse(validator.validate()) 650 | 651 | validator = self.validator(self.invalid_data2) 652 | validator.validate() 653 | self.assertFalse(validator.validate()) 654 | 655 | 656 | class UnblankTestCase(TestCase): 657 | def setUp(self): 658 | self.validator = Unblank 659 | self.valid_data = { 660 | 'msg': 'hello' 661 | } 662 | self.invalid_data = { 663 | 'msg': '' 664 | } 665 | 666 | self.invalid_data2 = { 667 | 'msg': '\r\n\r\n' 668 | } 669 | 670 | self.invalid_data3 = { 671 | 'msg2': 'test' 672 | } 673 | 674 | def test_valid(self): 675 | validator = self.validator(self.valid_data) 676 | validator.validate() 677 | self.assertTrue(validator.validate()) 678 | 679 | def test_invalid(self): 680 | validator = self.validator(self.invalid_data) 681 | validator.validate() 682 | self.assertTrue(validator.get_status()) 683 | 684 | validator = self.validator(self.invalid_data2) 685 | validator.validate() 686 | self.assertFalse(validator.get_status()) 687 | 688 | validator = self.validator(self.invalid_data3) 689 | validator.validate() 690 | self.assertTrue(validator.get_status()) 691 | 692 | 693 | class RequiredTestCase(TestCase): 694 | def setUp(self): 695 | self.validator = Required 696 | 697 | self.valid_data = { 698 | 'username': 'test' 699 | } 700 | 701 | self.invalid_data = { 702 | 'username': '' 703 | } 704 | 705 | self.message = { 706 | 'username': { 707 | 'required': 'username is required' 708 | } 709 | } 710 | 711 | def test_valid(self): 712 | validator = self.validator(self.valid_data) 713 | self.assertTrue(validator.validate()) 714 | 715 | def test_invalid(self): 716 | validator = self.validator(self.invalid_data) 717 | self.assertFalse(validator.validate()) 718 | message = validator.get_message() 719 | self.assertDictEqual(message, self.message) 720 | 721 | 722 | class AcceptedTestCase(TestCase): 723 | def setUp(self): 724 | self.validator = Accepted 725 | self.valid_data = { 726 | 'remember': 'yes' 727 | } 728 | self.invalid_data = { 729 | 'remember': 'none' 730 | } 731 | 732 | self.message = { 733 | 'remember': { 734 | 'accepted': 'input of none is not accepted in yes, no, true, false, 0, 1' 735 | } 736 | } 737 | 738 | def test_valid(self): 739 | validator = self.validator(self.valid_data) 740 | self.assertTrue(validator.validate()) 741 | 742 | def test_invalid(self): 743 | validator = self.validator(self.invalid_data) 744 | self.assertFalse(validator.validate()) 745 | message = validator.get_message() 746 | self.assertDictEqual(message, self.message) 747 | 748 | 749 | class AcceptedCustomTestCase(TestCase): 750 | def setUp(self): 751 | self.validator = AcceptedCustom 752 | self.valid_data = { 753 | 'remember': 'shi' 754 | } 755 | 756 | self.invalid_data = { 757 | 'remember': 'bushi' 758 | } 759 | 760 | self.message = { 761 | 'remember': { 762 | 'accepted': 'you just input bushi' 763 | } 764 | } 765 | 766 | def test_valid(self): 767 | validator = self.validator(self.valid_data) 768 | self.assertTrue(validator.validate()) 769 | 770 | def test_invalid(self): 771 | validator = self.validator(self.invalid_data) 772 | self.assertFalse(validator.validate()) 773 | self.assertDictEqual(validator.get_message(), self.message) 774 | 775 | 776 | class DateTestCase(TestCase): 777 | def setUp(self): 778 | self.validator = Date 779 | self.valid_data = { 780 | 'birthday': '1990-12-12' 781 | } 782 | self.invalid_data = { 783 | 'birthday': 'not a date' 784 | } 785 | 786 | self.message = { 787 | 'birthday': { 788 | 'date': 'date format is invalid' 789 | } 790 | } 791 | 792 | def test_vald(self): 793 | validator = self.validator(self.valid_data) 794 | self.assertTrue(validator.validate()) 795 | 796 | def test_invalid(self): 797 | validator = self.validator(self.invalid_data) 798 | self.assertFalse(validator.validate()) 799 | self.assertDictEqual(validator.get_message(), self.message) 800 | 801 | 802 | class DateCustomTestCase(TestCase): 803 | def setUp(self): 804 | self.validator = DateCustom 805 | self.valid_data = { 806 | 'birthday': '1990' 807 | } 808 | self.invalid_data = { 809 | 'birthday': 'not a date' 810 | } 811 | 812 | self.message = { 813 | 'birthday': { 814 | 'date': 'date format is not ok' 815 | } 816 | } 817 | 818 | def test_valid(self): 819 | validator = self.validator(self.valid_data) 820 | self.assertTrue(validator.validate()) 821 | 822 | def test_invalid(self): 823 | validator = self.validator(self.invalid_data) 824 | self.assertFalse(validator.validate()) 825 | self.assertDictEqual(validator.get_message(), self.message) 826 | 827 | 828 | class DateBeforeTestCase(TestCase): 829 | def setUp(self): 830 | self.validator = DateBefore 831 | self.valid_data = { 832 | 'expired_at': '1982-11-30' 833 | } 834 | 835 | self.invalid_data = { 836 | 'expired_at': '1991-04-25' 837 | } 838 | 839 | self.message = { 840 | 'expired_at': { 841 | 'date_before': 'date is not before 1990-12-12' 842 | } 843 | } 844 | 845 | def test_valid(self): 846 | validator = self.validator(self.valid_data) 847 | self.assertTrue(validator.validate()) 848 | 849 | def test_invalid(self): 850 | validator = self.validator(self.invalid_data) 851 | self.assertFalse(validator.validate()) 852 | self.assertDictEqual(self.message, validator.get_message()) 853 | 854 | 855 | class DateBeforeCustomTestCase(TestCase): 856 | def setUp(self): 857 | self.validator = DateBeforeCustom 858 | self.valid_data = { 859 | 'expired_at': '1989' 860 | } 861 | self.invalid_data = { 862 | 'expired_at': '1991' 863 | } 864 | self.message = { 865 | 'expired_at': { 866 | 'date_before': 'date is not before 1990' 867 | } 868 | } 869 | 870 | def test_valid(self): 871 | validator = self.validator(self.valid_data) 872 | self.assertTrue(validator.validate()) 873 | 874 | def test_invalid(self): 875 | validator = self.validator(self.invalid_data) 876 | self.assertFalse(validator.validate()) 877 | self.assertDictEqual(self.message, validator.get_message()) 878 | 879 | 880 | class DateAfterTestCase(TestCase): 881 | def setUp(self): 882 | self.validator = DateAfter 883 | self.valid_data = { 884 | 'due_at': '1991-04-25' 885 | } 886 | self.invalid_data = { 887 | 'due_at': '1982-11-30' 888 | } 889 | self.message = { 890 | 'due_at': { 891 | 'date_after': 'date is not after 1990-12-12' 892 | } 893 | } 894 | 895 | def test_valid(self): 896 | validator = self.validator(self.valid_data) 897 | self.assertTrue(validator.validate()) 898 | 899 | def test_invalid(self): 900 | validator = self.validator(self.invalid_data) 901 | self.assertFalse(validator.validate()) 902 | self.assertDictEqual(validator.get_message(), self.message) 903 | 904 | 905 | class DateAfterCustomTestCase(TestCase): 906 | def setUp(self): 907 | self.validator = DateAfterCustom 908 | self.valid_data = { 909 | 'due_at': '1991' 910 | } 911 | self.invalid_data = { 912 | 'due_at': '1989' 913 | } 914 | self.message = { 915 | 'due_at': { 916 | 'date_after': 'date is not after 1990' 917 | } 918 | } 919 | 920 | def test_valid(self): 921 | validator = self.validator(self.valid_data) 922 | self.assertTrue(validator.validate()) 923 | 924 | def test_invalid(self): 925 | validator = self.validator(self.invalid_data) 926 | self.assertFalse(validator.validate()) 927 | self.assertDictEqual(validator.get_message(), self.message) 928 | 929 | 930 | class DateRangeTestCase(TestCase): 931 | def setUp(self): 932 | self.validator = DateRange 933 | self.valid_data = { 934 | 'period': '1991-01-01' 935 | } 936 | 937 | self.invalid_data = { 938 | 'period': '1992-12-12' 939 | } 940 | 941 | self.message = { 942 | 'period': { 943 | 'date_range': 'date is not in range of 1990-12-12 to 1991-12-12' 944 | } 945 | } 946 | 947 | def test_valid(self): 948 | validator = self.validator(self.valid_data) 949 | self.assertTrue(validator.validate()) 950 | 951 | def test_invalid(self): 952 | validator = self.validator(self.invalid_data) 953 | self.assertFalse(validator.validate()) 954 | message = validator.get_message() 955 | self.assertDictEqual(self.message, message) 956 | 957 | 958 | class DatetimeTestCase(TestCase): 959 | def setUp(self): 960 | self.validator = Datetime 961 | self.valid_data = { 962 | 'now': '1987-10-5 12:55:01' 963 | } 964 | self.invalid_data = { 965 | 'now': 'not a datetime string' 966 | } 967 | 968 | self.message = { 969 | 'now': { 970 | 'datetime': 'it is not a datetime format string' 971 | } 972 | } 973 | 974 | def test_valid(self): 975 | validator = self.validator(self.valid_data) 976 | self.assertTrue(validator.validate()) 977 | 978 | def test_invalid(self): 979 | validator = self.validator(self.invalid_data) 980 | self.assertFalse(validator.validate()) 981 | message = validator.get_message() 982 | self.assertDictEqual(message, self.message) 983 | 984 | 985 | class DatetimeBeforeTestCase(TestCase): 986 | def setUp(self): 987 | self.validator = DatetimeBefore 988 | self.valid_data = { 989 | 'due_at': '1989-11-11 12:12:00' 990 | } 991 | self.invalid_data = { 992 | 'due_at': '2018-06-01 12:55:01' 993 | } 994 | 995 | self.message = { 996 | 'due_at': { 997 | 'datetime_before': 'the input is not before 1990-12-12 15:31:10' 998 | } 999 | } 1000 | 1001 | def test_valid(self): 1002 | validator = self.validator(self.valid_data) 1003 | self.assertTrue(validator.validate()) 1004 | 1005 | def test_invalid(self): 1006 | validator = self.validator(self.invalid_data) 1007 | self.assertFalse(validator.validate()) 1008 | message = validator.get_message() 1009 | self.assertDictEqual(message, self.message) 1010 | 1011 | 1012 | class DatetimeAfterTestCase(TestCase): 1013 | def setUp(self): 1014 | self.validator = DatetimeAfter 1015 | self.valid_data = { 1016 | 'after_at': '2011-11-11 12:12:00' 1017 | } 1018 | self.invalid_data = { 1019 | 'after_at': '1955-11-11 12:12:00' 1020 | } 1021 | self.message = { 1022 | 'after_at': { 1023 | 'datetime_after': 'the input is not after 1990-12-12 15:31:10' 1024 | } 1025 | } 1026 | 1027 | def test_valid(self): 1028 | validator = self.validator(self.valid_data) 1029 | self.assertTrue(validator.validate()) 1030 | 1031 | def test_invalid(self): 1032 | validator = self.validator(self.invalid_data) 1033 | self.assertFalse(validator.validate()) 1034 | message = validator.get_message() 1035 | self.assertDictEqual(message, self.message) 1036 | 1037 | 1038 | class DatetimeRangeTestCase(TestCase): 1039 | def setUp(self): 1040 | self.validator = DatetimeRange 1041 | self.valid_data = { 1042 | 'range_at': '1991-01-12 15:31:10' 1043 | } 1044 | self.invalid_data = { 1045 | 'range_at': '1988-01-12 15:31:10' 1046 | } 1047 | 1048 | self.message = { 1049 | 'range_at': { 1050 | 'datetime_range': 'the input is not after 1990-12-12 15:31:10 to 1991-12-12 15:31:10' 1051 | } 1052 | } 1053 | 1054 | def test_valid(self): 1055 | validator = self.validator(self.valid_data) 1056 | self.assertTrue(validator.validate()) 1057 | 1058 | def test_invalid(self): 1059 | validator = self.validator(self.invalid_data) 1060 | self.assertFalse(validator.validate()) 1061 | message = validator.get_message() 1062 | self.assertDictEqual(message, self.message) 1063 | 1064 | 1065 | class ActiveUrlTestCase(TestCase): 1066 | def setUp(self): 1067 | self.validator = ActiveUrl 1068 | self.valid_data = { 1069 | 'url': 'baidu.com' 1070 | } 1071 | self.invalid_data = { 1072 | 'url': 'www.sfsdf.sdffs' 1073 | } 1074 | self.message = { 1075 | 'url': { 1076 | 'active_url': 'it is not a active url' 1077 | } 1078 | } 1079 | 1080 | def test_valid(self): 1081 | validator = self.validator(self.valid_data) 1082 | self.assertTrue(validator.validate()) 1083 | 1084 | 1085 | class NumberciTestCase(TestCase): 1086 | def setUp(self): 1087 | self.validator = Numberic 1088 | self.valid_data = { 1089 | 'number': '123' 1090 | } 1091 | self.invalid_data = { 1092 | 'number': 'abcdef' 1093 | } 1094 | 1095 | self.message = { 1096 | 'number': { 1097 | 'numberic': 'abcdef of number is not numberic' 1098 | } 1099 | } 1100 | 1101 | def test_valid(self): 1102 | validator = self.validator(self.valid_data) 1103 | self.assertTrue(validator.validate()) 1104 | 1105 | def tst_invalid(self): 1106 | validator = self.validator(self.invalid_data) 1107 | self.assertFalse(validator.validate()) 1108 | message = validator.get_message() 1109 | self.assertDictEqual(message, self.message) 1110 | 1111 | 1112 | class DigitsTestCase(TestCase): 1113 | def setUp(self): 1114 | self.validator = Digits 1115 | self.valid_data = { 1116 | 'card': '12345' 1117 | } 1118 | self.invalid_data = { 1119 | 'card': 'abcdef' 1120 | } 1121 | self.message = { 1122 | 'card': { 1123 | 'digits': 'abcdef of card is not digits' 1124 | } 1125 | } 1126 | 1127 | def test_valid(self): 1128 | validator = self.validator(self.valid_data) 1129 | self.assertTrue(validator.validate()) 1130 | 1131 | def test_invalid(self): 1132 | validator = self.validator(self.invalid_data) 1133 | self.assertFalse(validator.validate()) 1134 | message = validator.get_message() 1135 | self.assertDictEqual(message, self.message) 1136 | 1137 | 1138 | class RegexTestCase(TestCase): 1139 | def setUp(self): 1140 | self.validator = Regex 1141 | self.valid_data = { 1142 | 'identity': 'ab12' 1143 | } 1144 | self.invalid_data = { 1145 | 'identity': '1' 1146 | } 1147 | self.message = { 1148 | 'identity': { 1149 | 'regex': '1 of identity is not match the pattern [0-9a-z]{3,5}' 1150 | } 1151 | } 1152 | 1153 | def test_valid(self): 1154 | validator = self.validator(self.valid_data) 1155 | self.assertTrue(validator.validate()) 1156 | 1157 | def test_invalid(self): 1158 | validator = self.validator(self.invalid_data) 1159 | self.assertFalse(validator.validate()) 1160 | message = validator.get_message() 1161 | self.assertDictEqual(message, self.message) 1162 | 1163 | 1164 | class EmailTestCase(TestCase): 1165 | def setUp(self): 1166 | self.validator = Email 1167 | self.valid_data = { 1168 | 'email': 'younger.shen@hotmail.com' 1169 | } 1170 | self.invalid_data = { 1171 | 'email': 'i am a little bear' 1172 | } 1173 | self.message = { 1174 | 'email': { 1175 | 'email': 'i am a little bear is not an email address' 1176 | } 1177 | } 1178 | 1179 | def test_valid(self): 1180 | validator = self.validator(self.valid_data) 1181 | self.assertTrue(validator.validate()) 1182 | 1183 | def test_invalid(self): 1184 | validator = self.validator(self.invalid_data) 1185 | self.assertFalse(validator.validate()) 1186 | message = validator.get_message() 1187 | self.assertDictEqual(message, self.message) 1188 | 1189 | 1190 | class MinLengthTestCase(TestCase): 1191 | def setUp(self): 1192 | self.validator = MinLength 1193 | self.valid_data = { 1194 | 'username': 'abacdef' 1195 | } 1196 | self.invalid_data = { 1197 | 'username': 'a' 1198 | } 1199 | self.message = { 1200 | 'username': { 1201 | 'min_length': 'a of username is shotter than 4' 1202 | } 1203 | } 1204 | 1205 | def test_valid(self): 1206 | validator = self.validator(self.valid_data) 1207 | self.assertTrue(validator.validate()) 1208 | 1209 | def test_invalid(self): 1210 | validator = self.validator(self.invalid_data) 1211 | self.assertFalse(validator.validate()) 1212 | message = validator.get_message() 1213 | self.assertDictEqual(message, self.message) 1214 | 1215 | 1216 | class MaxLengthTestCase(TestCase): 1217 | def setUp(self): 1218 | self.validator = MaxLength 1219 | self.valid_data = { 1220 | 'username': 'abacde' 1221 | } 1222 | self.invalid_data = { 1223 | 'username': 'abcdefgh' 1224 | } 1225 | self.message = { 1226 | 'username': { 1227 | 'max_length': 'abcdefgh of username is longger than 7' 1228 | } 1229 | } 1230 | 1231 | def test_valid(self): 1232 | validator = self.validator(self.valid_data) 1233 | self.assertTrue(validator.validate()) 1234 | 1235 | def test_invalid(self): 1236 | validator = self.validator(self.invalid_data) 1237 | self.assertFalse(validator.validate()) 1238 | message = validator.get_message() 1239 | self.assertDictEqual(message, self.message) 1240 | 1241 | 1242 | class IDSTestCase(TestCase): 1243 | def setUp(self): 1244 | self.validator = IDS 1245 | self.valid_data = { 1246 | 'ids': '1,2,3,4' 1247 | } 1248 | self.invalid_data = { 1249 | 'ids': 'a,b,c,d' 1250 | } 1251 | self.message = { 1252 | 'ids': { 1253 | 'ids': 'a,b,c,d of ids is not a id series' 1254 | } 1255 | } 1256 | 1257 | def test_valid(self): 1258 | validator = IDS(self.valid_data) 1259 | self.assertTrue(validator.validate()) 1260 | 1261 | def test_invalid(self): 1262 | validator = IDS(self.invalid_data) 1263 | self.assertFalse(validator.validate()) 1264 | message = validator.get_message() 1265 | self.assertDictEqual(message, self.message) 1266 | 1267 | 1268 | class CellphoneTestCase(TestCase): 1269 | def setUp(self): 1270 | self.validator = Cellphone 1271 | self.valid_data = { 1272 | 'cellphone': '13811754531' 1273 | } 1274 | 1275 | self.invalid_data = { 1276 | '123456789123456789' 1277 | } 1278 | 1279 | self.message = { 1280 | 'cellphone': { 1281 | 'cellphone': '123456789123456789 is not a cellphone number' 1282 | } 1283 | } 1284 | 1285 | def test_valid(self): 1286 | validator = self.validator(self.valid_data) 1287 | self.assertTrue(validator.validate()) 1288 | 1289 | def tst_invalid(self): 1290 | validator = self.validator(self.invalid_data) 1291 | self.assertFalse(validator.validate()) 1292 | message = validator.get_message() 1293 | self.assertDictEqual(message, self.message) 1294 | 1295 | 1296 | class AlphabetTestCase(TestCase): 1297 | def setUp(self): 1298 | self.validator = Alphabet 1299 | self.valid_data = { 1300 | 'alphabet': 'abcdef' 1301 | } 1302 | self.invalid_data = { 1303 | 'alphabet': '123456' 1304 | } 1305 | 1306 | self.message = { 1307 | 'alphabet': { 1308 | 'alphabet': '123456 of alphabet is not alphabet' 1309 | } 1310 | } 1311 | 1312 | def test_valid(self): 1313 | validator = self.validator(self.valid_data) 1314 | self.assertTrue(validator.validate()) 1315 | 1316 | def test_invalid(self): 1317 | validator = self.validator(self.invalid_data) 1318 | self.assertFalse(validator.validate()) 1319 | message = validator.get_message() 1320 | self.assertDictEqual(message, self.message) 1321 | 1322 | 1323 | class SwitchTestCase(TestCase): 1324 | def setUp(self): 1325 | self.validator = Switch 1326 | self.valid_data = { 1327 | 'accepted': 'ok' 1328 | } 1329 | 1330 | self.invalid_data = { 1331 | 'accepted': 'bad' 1332 | } 1333 | self.message = { 1334 | 'accepted': { 1335 | 'switch': 'bad of accepted is not in [ok,good,awesome]' 1336 | } 1337 | } 1338 | 1339 | def test_valid(self): 1340 | validator = self.validator(self.valid_data) 1341 | self.assertTrue(validator.validate()) 1342 | 1343 | def test_invalid(self): 1344 | validator = self.validator(self.invalid_data) 1345 | self.assertFalse(validator.validate()) 1346 | message = validator.get_message() 1347 | self.assertDictEqual(message, self.message) 1348 | 1349 | 1350 | class UniqueTestCase(TestCase): 1351 | def setUp(self): 1352 | from django.contrib.auth.models import User 1353 | User.objects.create_user('test', 'test') 1354 | 1355 | self.validator = Unique 1356 | self.valid_data = { 1357 | 'user_id': '2' 1358 | } 1359 | self.invalid_data = { 1360 | 'user_id': '1' 1361 | } 1362 | 1363 | self.message = { 1364 | 'user_id': { 1365 | 'unique': '1 of AUTH_USER_MODEL with id is not unique' 1366 | } 1367 | } 1368 | 1369 | def test_valid(self): 1370 | validator = self.validator(self.valid_data) 1371 | self.assertTrue(validator.validate()) 1372 | 1373 | def test_invalid(self): 1374 | validator = self.validator(self.invalid_data) 1375 | self.assertFalse(validator.validate()) 1376 | message = validator.get_message() 1377 | self.assertDictEqual(message, self.message) 1378 | 1379 | 1380 | class SizeTestCase(TestCase): 1381 | def setUp(self): 1382 | self.avatar = self.get_avatar() 1383 | self.validator = Size 1384 | 1385 | self.valid_data = { 1386 | 'username': 'abcde', 1387 | 'number': '5', 1388 | 'profile': 'age,12', 1389 | 'avatar': self.avatar 1390 | } 1391 | 1392 | self.invalid_data = { 1393 | 'username': '', 1394 | 'number': '', 1395 | 'profile': '', 1396 | 'avatar': '' 1397 | } 1398 | 1399 | def test_valid(self): 1400 | validator = self.validator(self.valid_data) 1401 | self.assertTrue(validator.validate()) 1402 | 1403 | def get_avatar(self): 1404 | buffer = BytesIO() 1405 | self.get_temp_file(buffer) 1406 | avatar = InMemoryUploadedFile( 1407 | file=buffer, 1408 | field_name='avatar', 1409 | name='avatar', 1410 | size=len(buffer.getvalue()), 1411 | charset=None, 1412 | content_type='image/jpeg' 1413 | ) 1414 | self.assertTrue(avatar.content_type) 1415 | return avatar 1416 | 1417 | @staticmethod 1418 | def get_temp_file(buffer): 1419 | with open('tests/assets/linux.jpeg', mode='rb') as f: 1420 | buffer.write(f.read()) 1421 | 1422 | 1423 | class MinTestCase(TestCase): 1424 | def setUp(self): 1425 | self.validator = Min 1426 | self.valid_data = { 1427 | 'age': 20 1428 | } 1429 | self.invalid_data = { 1430 | 'age': 10 1431 | } 1432 | 1433 | self.message = { 1434 | 'age': { 1435 | 'min': 'sorry we do not support service to people who is under 15.' 1436 | } 1437 | } 1438 | 1439 | def test_valid(self): 1440 | validator = self.validator(self.valid_data) 1441 | self.assertTrue(validator.validate()) 1442 | 1443 | def test_invalid(self): 1444 | validator = self.validator(self.invalid_data) 1445 | self.assertFalse(validator.validate()) 1446 | message = validator.get_message() 1447 | self.assertDictEqual(message, self.message) 1448 | 1449 | 1450 | class AlphaDashTestCase(TestCase): 1451 | def setUp(self): 1452 | self.validator = AlphaDash 1453 | self.valid_data = { 1454 | 'username': 'abc_def' 1455 | } 1456 | self.invalid_data = { 1457 | 'username': '#%#@' 1458 | } 1459 | self.message = { 1460 | 'username': { 1461 | 'alpha_dash': 'username should only includes alphabet and dash characters.' 1462 | } 1463 | } 1464 | 1465 | def test_valid(self): 1466 | validator = self.validator(self.valid_data) 1467 | self.assertTrue(validator.validate()) 1468 | 1469 | def test_invalid(self): 1470 | validator = self.validator(self.invalid_data) 1471 | self.assertFalse(validator.validate()) 1472 | message = validator.get_message() 1473 | self.assertDictEqual(message, self.message) 1474 | 1475 | 1476 | class MaxTestCase(TestCase): 1477 | def setUp(self): 1478 | self.validator = Max 1479 | self.valid_data = { 1480 | 'age': 15 1481 | } 1482 | self.invalid_data = { 1483 | 'age': 55 1484 | } 1485 | self.message = { 1486 | 'age': { 1487 | 'max': 'sorry we do not support service to people who is older than 50.' 1488 | } 1489 | } 1490 | 1491 | def test_valid(self): 1492 | validator = self.validator(self.valid_data) 1493 | self.assertTrue(validator.validate()) 1494 | 1495 | def test_invalid(self): 1496 | validator = self.validator(self.invalid_data) 1497 | self.assertFalse(validator.validate()) 1498 | message = validator.get_message() 1499 | self.assertDictEqual(message, self.message) 1500 | 1501 | 1502 | class FileTestCase(TestCase): 1503 | def setUp(self): 1504 | self.validator = File 1505 | self.valid_data = { 1506 | 'file': self.get_file() 1507 | } 1508 | self.invalid_data = { 1509 | 'file': self.get_file('tgz') 1510 | } 1511 | 1512 | self.message = { 1513 | 'file': { 1514 | 'file': 'file is not allowed to upload' 1515 | } 1516 | } 1517 | 1518 | def test_valid(self): 1519 | validator = self.validator(self.valid_data) 1520 | self.assertTrue(validator.validate()) 1521 | 1522 | def test_invalid(self): 1523 | validator = self.validator(self.invalid_data) 1524 | self.assertFalse(validator.validate()) 1525 | message = validator.get_message() 1526 | self.assertDictEqual(message, self.message) 1527 | 1528 | @staticmethod 1529 | def get_file(_type='jpeg'): 1530 | buffer = BytesIO() 1531 | with open('tests/assets/linux.' + _type, mode='rb') as f: 1532 | buffer.write(f.read()) 1533 | 1534 | file = InMemoryUploadedFile( 1535 | file=buffer, 1536 | field_name='file', 1537 | name='file.' + _type, 1538 | size=len(buffer.getvalue()), 1539 | charset=None, 1540 | content_type='image/jpeg' 1541 | ) 1542 | return file 1543 | 1544 | 1545 | class CustomRuleTestCase(TestCase): 1546 | def setUp(self): 1547 | self.extra_rules = { 1548 | TestRule.get_name(): TestRule 1549 | } 1550 | self.validator = TestRuleValidator 1551 | self.message = { 1552 | 'name': { 1553 | 'test_rule': 'test custom rule failed' 1554 | } 1555 | } 1556 | self.valid_data = { 1557 | 'name': 'test', 1558 | } 1559 | self.invalid_data = { 1560 | 'name': 'toast' 1561 | } 1562 | 1563 | def test_valid(self): 1564 | validator = self.validator(extra_rules=self.extra_rules, data=self.valid_data) 1565 | self.assertTrue(validator.validate()) 1566 | 1567 | def test_invalid(self): 1568 | validator = self.validator(extra_rules=self.extra_rules, data=self.invalid_data) 1569 | self.assertFalse(validator.validate()) 1570 | message = validator.get_message() 1571 | self.assertDictEqual(message, self.message) 1572 | 1573 | 1574 | class UsernameTestCase(TestCase): 1575 | def setUp(self): 1576 | self.validator = Username 1577 | self.valid_data = { 1578 | 'username': 'abc8848cba' 1579 | } 1580 | self.invalid_data = { 1581 | 'username': '123ABCdef' 1582 | } 1583 | self.message = { 1584 | 'username': { 1585 | 'username': 'the input 123ABCdef is not a proper username.' 1586 | } 1587 | } 1588 | 1589 | def test_valid(self): 1590 | validator = self.validator(self.valid_data) 1591 | self.assertTrue(validator.validate()) 1592 | 1593 | def test_invalid(self): 1594 | valiadtor = self.validator(self.invalid_data) 1595 | self.assertFalse(valiadtor.validate()) 1596 | message = valiadtor.get_message() 1597 | self.assertDictEqual(message, self.message) 1598 | 1599 | 1600 | class PasswordTestCase(TestCase): 1601 | def setUp(self): 1602 | self.validator1 = PasswordLow 1603 | self.validator2 = PasswordMiddle 1604 | self.validator3 = PasswordHigh 1605 | 1606 | self.valid_data1 = { 1607 | 'password': '1234567设定' 1608 | } 1609 | self.valid_data2 = { 1610 | 'password': 'abcDEF123' 1611 | } 1612 | self.valid_data3 = { 1613 | 'password': 'ABCdef123!@#' 1614 | } 1615 | self.invalid_data1 = { 1616 | 'password': '123' 1617 | } 1618 | self.invalid_data2 = { 1619 | 'password': 'abcdef123' 1620 | } 1621 | self.invalid_data3 = { 1622 | 'password': 'abcdef1234' 1623 | } 1624 | 1625 | def test_low(self): 1626 | validator = self.validator1(self.valid_data1) 1627 | self.assertTrue(validator.validate()) 1628 | 1629 | validator = self.validator1(self.invalid_data1) 1630 | self.assertFalse(validator.validate()) 1631 | 1632 | def test_middle(self): 1633 | validator = self.validator2(self.valid_data2) 1634 | self.assertTrue(validator.validate()) 1635 | 1636 | validator = self.validator2(self.invalid_data2) 1637 | self.assertFalse(validator.validate()) 1638 | 1639 | def test_high(self): 1640 | validator = self.validator3(self.valid_data3) 1641 | self.assertTrue(validator.validate()) 1642 | 1643 | validator = self.validator3(self.invalid_data3) 1644 | self.assertFalse(validator.validate()) 1645 | 1646 | 1647 | class ASCIITestCase(TestCase): 1648 | def setUp(self): 1649 | self.validator = ASCII 1650 | self.valid_data = { 1651 | 'seq': 'a ' 1652 | } 1653 | self.invalid_data = { 1654 | 'seq': '你好世界' 1655 | } 1656 | 1657 | self.message = { 1658 | 'seq': { 1659 | 'ascii': 'the input 你好世界 value is not a proper ASCII character.' 1660 | } 1661 | } 1662 | 1663 | def test_valid(self): 1664 | validator = self.validator(self.valid_data) 1665 | self.assertTrue(validator.validate()) 1666 | 1667 | def test_invalid(self): 1668 | validator = self.validator(self.invalid_data) 1669 | self.assertFalse(validator.validate()) 1670 | message = validator.get_message() 1671 | self.assertDictEqual(message, self.message) 1672 | 1673 | 1674 | class BooleanTestCase(TestCase): 1675 | def setUp(self): 1676 | self.validator = Boolean 1677 | self.valid_data = { 1678 | 'remember_me': 'true' 1679 | } 1680 | 1681 | self.invalid_data = { 1682 | 'remember_me': 'haha' 1683 | } 1684 | 1685 | self.message = { 1686 | 'remember_me': { 1687 | 'boolean': 'haha is not a boolean type value.' 1688 | } 1689 | } 1690 | 1691 | def test_valid(self): 1692 | validator = self.validator(self.valid_data) 1693 | self.assertTrue(validator.validate()) 1694 | 1695 | def test_invalid(self): 1696 | validator = self.validator(self.invalid_data) 1697 | self.assertFalse(validator.validate()) 1698 | message = validator.get_message() 1699 | self.assertDictEqual(message, self.message) 1700 | 1701 | 1702 | class BetweenTestCase(TestCase): 1703 | def setUp(self): 1704 | self.validator = Between 1705 | self.valid_data = { 1706 | 'age': 15 1707 | } 1708 | self.invalid_data = { 1709 | 'age': 25 1710 | } 1711 | self.message = { 1712 | 'age': { 1713 | 'between': '25 is not between 10 to 20' 1714 | } 1715 | } 1716 | 1717 | def test_valid(self): 1718 | validator = self.validator(self.valid_data) 1719 | self.assertTrue(validator.validate()) 1720 | 1721 | def test_invalid(self): 1722 | validator = self.validator(self.invalid_data) 1723 | self.assertFalse(validator.validate()) 1724 | message = validator.get_message() 1725 | self.assertDictEqual(message, self.message) 1726 | 1727 | 1728 | class ArrayTestCase(TestCase): 1729 | def setUp(self): 1730 | self.validator = Array 1731 | self.valid_data = { 1732 | 'ids': '1, 2, 3, 4' 1733 | } 1734 | self.invalid_data = { 1735 | 'ids': 'abcdef' 1736 | } 1737 | 1738 | self.message = { 1739 | 'ids': { 1740 | 'array': 'abcdef is not a array type series.' 1741 | } 1742 | } 1743 | 1744 | def test_valid(self): 1745 | validator = self.validator(self.valid_data) 1746 | self.assertTrue(validator.validate()) 1747 | 1748 | def test_invalid(self): 1749 | validator = self.validator(self.invalid_data) 1750 | self.assertFalse(validator.validate()) 1751 | message = validator.get_message() 1752 | self.assertDictEqual(message, self.message) 1753 | 1754 | 1755 | class AlphaNumberTest(TestCase): 1756 | def setUp(self): 1757 | self.validator = AlphaNumber 1758 | self.valid_data = { 1759 | 'code': 'abc123' 1760 | } 1761 | self.invalid_data = { 1762 | 'code': '密码' 1763 | } 1764 | self.message = { 1765 | 'code': { 1766 | 'alpha_number': '密码 is not a alpha number type series.' 1767 | } 1768 | } 1769 | 1770 | def test_valid(self): 1771 | validator = self.validator(self.valid_data) 1772 | self.assertTrue(validator.validate()) 1773 | 1774 | def test_invalid(self): 1775 | validator = self.validator(self.invalid_data) 1776 | self.assertFalse(validator.validate()) 1777 | message = validator.get_message() 1778 | self.assertDictEqual(message, self.message) 1779 | 1780 | 1781 | class SameTestCase(TestCase): 1782 | def setUp(self): 1783 | self.validator = Same 1784 | self.valid_data = { 1785 | 'password': 'abcd1234', 1786 | 'password_confirm': 'abcd1234' 1787 | } 1788 | 1789 | self.invalid_data = { 1790 | 'password': 'abcd', 1791 | 'password_confirm': '1234' 1792 | } 1793 | 1794 | def test_valid(self): 1795 | validator = self.validator(self.valid_data) 1796 | self.assertTrue(validator.validate()) 1797 | 1798 | 1799 | class DecimalTestCase(TestCase): 1800 | def setUp(self): 1801 | self.validator = Decimal 1802 | self.valid_data = { 1803 | 'price': '-123.456' 1804 | } 1805 | self.valid_data1 = { 1806 | 'price': '+123.456' 1807 | } 1808 | self.valid_data2 = { 1809 | 'price': 123.456 1810 | } 1811 | 1812 | self.valid_data3 = { 1813 | 'price': -123.456 1814 | } 1815 | 1816 | self.invalid_data = { 1817 | 'price': 'abcdef' 1818 | } 1819 | 1820 | def test_valid(self): 1821 | validator = self.validator(self.valid_data) 1822 | self.assertTrue(validator.validate()) 1823 | 1824 | validator = self.validator(self.valid_data1) 1825 | self.assertTrue(validator.validate()) 1826 | 1827 | validator = self.validator(self.valid_data2) 1828 | self.assertTrue(validator.validate()) 1829 | 1830 | validator = self.validator(self.valid_data3) 1831 | self.assertTrue(validator.validate()) 1832 | 1833 | validator = self.validator(self.invalid_data) 1834 | self.assertFalse(validator.validate()) 1835 | 1836 | 1837 | class ExistTestCase(TestCase): 1838 | def setUp(self): 1839 | self.setup_users() 1840 | self.validator = Exist 1841 | self.valid_data = { 1842 | 'uid': '1' 1843 | } 1844 | self.invalid_data = { 1845 | 'uid': 'test' 1846 | } 1847 | 1848 | def test_valid(self): 1849 | validator = self.validator(self.valid_data) 1850 | self.assertTrue(validator.validate()) 1851 | 1852 | validator = self.validator(self.invalid_data) 1853 | self.assertFalse(validator.validate()) 1854 | 1855 | @staticmethod 1856 | def setup_users(): 1857 | from django.contrib.auth.models import User 1858 | User.objects.create_user(username='youngershen', 1859 | email='shenyangang@163.com', 1860 | password='123456789') 1861 | 1862 | 1863 | class UniqueAgainstTestCase(TestCase): 1864 | def setUp(self): 1865 | self.setup_users() 1866 | self.validator = UniqueAgainst 1867 | self.valid_data = { 1868 | 'username': 'youngershen' 1869 | } 1870 | self.invalid_data = { 1871 | 'username': 'bear' 1872 | } 1873 | 1874 | def test_valid(self): 1875 | validator = self.validator(self.valid_data) 1876 | self.assertTrue(validator.validate()) 1877 | 1878 | validator = self.validator(self.invalid_data) 1879 | self.assertFalse(validator.validate()) 1880 | 1881 | @staticmethod 1882 | def setup_users(): 1883 | from django.contrib.auth.models import User 1884 | User.objects.create_user(username='youngershen', 1885 | email='shenyangang@163.com', 1886 | password='123456789') 1887 | 1888 | User.objects.create_user(username='bear', 1889 | email='shenyangang@163.com', 1890 | password='123456789') 1891 | 1892 | 1893 | class PrintableASCIITestCase(TestCase): 1894 | def setUp(self): 1895 | self.validator = PrintableASCII 1896 | self.valid_data = { 1897 | 'username': 'abcdef@123456' 1898 | } 1899 | self.invalid_data = { 1900 | 'username': chr(555) 1901 | } 1902 | self.valid_data_blank = { 1903 | 'username': ' ' 1904 | } 1905 | self.message = { 1906 | 'username': { 1907 | 'pascii': '用户名不能为空' 1908 | } 1909 | } 1910 | 1911 | def test_valid(self): 1912 | validator = self.validator(self.valid_data) 1913 | self.assertTrue(validator.validate()) 1914 | 1915 | validator = self.validator(self.valid_data_blank) 1916 | self.assertTrue(validator.validate()) 1917 | 1918 | def test_invalid(self): 1919 | validator = self.validator(self.invalid_data) 1920 | self.assertFalse(validator.validate()) 1921 | message = validator.get_message() 1922 | self.assertDictEqual(message, self.message) 1923 | 1924 | validator = self.validator(self.valid_data_blank) 1925 | self.assertTrue(validator.validate()) 1926 | 1927 | 1928 | class PrintableASCIINoBlankTestCase(TestCase): 1929 | def setUp(self): 1930 | self.validator = PrintableASCIINoBlank 1931 | self.valid_data = { 1932 | 'username': 'abcdef@123456' 1933 | } 1934 | self.invalid_data = { 1935 | 'username': chr(555) 1936 | } 1937 | self.invalid_data_blank = { 1938 | 'username': ' ' 1939 | } 1940 | self.message = { 1941 | 'username': { 1942 | 'pascii': '用户名不能为空' 1943 | } 1944 | } 1945 | 1946 | def test_valid(self): 1947 | validator = self.validator(self.valid_data) 1948 | self.assertTrue(validator.validate()) 1949 | 1950 | def test_invalid(self): 1951 | validator = self.validator(self.invalid_data) 1952 | self.assertFalse(validator.validate()) 1953 | message = validator.get_message() 1954 | self.assertDictEqual(message, self.message) 1955 | 1956 | validator = self.validator(self.invalid_data_blank) 1957 | self.assertFalse(validator.validate()) 1958 | message = validator.get_message() 1959 | self.assertDictEqual(message, self.message) 1960 | -------------------------------------------------------------------------------- /validator/__init__.py: -------------------------------------------------------------------------------- 1 | # PROJECT : django-easy-validator 2 | # TIME : 18-1-2 上午9:44 3 | # AUTHOR : Younger Shen 4 | # EMAIL : younger.x.shen@gmail.com 5 | # CELL : 13811754531 6 | # WECHAT : 13811754531 7 | # https://github.com/youngershen/ 8 | 9 | 10 | from .validators import Validator, BaseRule 11 | 12 | __all__ = ['Validator', 'BaseRule'] 13 | -------------------------------------------------------------------------------- /validator/validators.py: -------------------------------------------------------------------------------- 1 | # PROJECT : django-easy-validator 2 | # TIME : 18-1-2 上午9:44 3 | # AUTHOR : Younger Shen 4 | # EMAIL : younger.x.shen@gmail.com 5 | # CELL : 13811754531 6 | # WECHAT : 13811754531 7 | # https://github.com/youngershen/ 8 | 9 | import re 10 | import socket 11 | import datetime 12 | from copy import deepcopy 13 | 14 | try: 15 | # django not installed 16 | from django import utils 17 | except ImportError: 18 | def _(text): 19 | return text 20 | else: 21 | try: 22 | # django not configured 23 | from django.core.exceptions import ImproperlyConfigured 24 | from django.utils.translation import gettext_lazy as _ 25 | except ImproperlyConfigured: 26 | def _(text): 27 | return text 28 | 29 | 30 | class RuleNotFoundError(Exception): 31 | message = _('{NAME} rule not found !!!') 32 | 33 | def __init__(self, name): 34 | self.name = name 35 | 36 | def __str__(self): 37 | return self.message.format(NAME=self.name) 38 | 39 | 40 | class RuleMissedParameterError(Exception): 41 | pass 42 | 43 | 44 | class InvalidRuleParamterError(Exception): 45 | pass 46 | 47 | 48 | class BaseRule: 49 | name = 'base_rule' 50 | message = _('{VALUE} of {FIELD} field is match rule {RULE_NAME}.') 51 | description = _('describe the propuse of the current rule.') 52 | parse_args = True 53 | 54 | def __init__(self, field_name, field_value, args, data=None, message=None): 55 | self.field_name = field_name 56 | self.field_value = field_value 57 | self.args = list(map(lambda d: d.strip(), args.split(','))) if self.parse_args else args 58 | self.status = True 59 | self.message = message if message else self.message 60 | self.data = data 61 | 62 | def check(self): 63 | if self.field_value: 64 | self.check_value() 65 | else: 66 | self.check_null() 67 | 68 | def check_value(self): 69 | raise NotImplementedError() 70 | 71 | def check_null(self): 72 | raise NotImplementedError() 73 | 74 | def get_status(self): 75 | return self.status 76 | 77 | def get_message(self): 78 | return self.message.format(FIELD=self.field_name, VALUE=self.field_value, RULE_NAME=self.name) 79 | 80 | def get_arg(self, index): 81 | if len(self.args) > index: 82 | return self.args[index] 83 | else: 84 | return None 85 | 86 | @classmethod 87 | def get_name(cls): 88 | return cls.name 89 | 90 | 91 | class Switch(BaseRule): 92 | name = 'switch' 93 | message = _('{VALUE} of {FIELD} is not in [{SWITCH}]') 94 | description = _('check if the value is in the params array.') 95 | 96 | def check_value(self): 97 | self.status = self.field_value in self._get_params() 98 | 99 | def check_null(self): 100 | pass 101 | 102 | def get_message(self): 103 | switch_str = ','.join(self._get_params()) 104 | return self.message.format(VALUE=self.field_value, 105 | FIELD=self.field_name, 106 | SWITCH=switch_str) 107 | 108 | def _get_params(self): 109 | return self.args if self.args else [] 110 | 111 | 112 | class Alphabet(BaseRule): 113 | name = 'alphabet' 114 | regex = r'[a-zA-Z]+' 115 | message = _('{VALUE} of {FIELD} is not alphabet') 116 | description = _('The field under validation must be entirely alphabetic characters.') 117 | 118 | def check_null(self): 119 | pass 120 | 121 | def check_value(self): 122 | self.status = True if re.match(self.regex, self.field_value) else False 123 | 124 | 125 | class Json(BaseRule): 126 | """ 127 | TODO add a rule to check json string 128 | """ 129 | 130 | 131 | class Length(BaseRule): 132 | """ 133 | TODO just to limit the length of the string 134 | """ 135 | name = 'length' 136 | 137 | 138 | class MinLength(BaseRule): 139 | name = 'min_length' 140 | message = _('{VALUE} of {FIELD} is shotter than {MIN}') 141 | description = _('check the field as a string and test the length if suites the given number') 142 | 143 | def check_null(self): 144 | pass 145 | 146 | def check_value(self): 147 | self.status = len(str(self.field_value)) >= int(self.args[0]) 148 | 149 | def get_message(self): 150 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, MIN=self.args[0]) 151 | 152 | 153 | class MaxLength(BaseRule): 154 | name = 'max_length' 155 | message = _('{VALUE} of {FIELD} is longger than {MAX}') 156 | description = _('check the field as a string and test the length if suites the given number') 157 | 158 | def check_null(self): 159 | pass 160 | 161 | def check_value(self): 162 | self.status = len(str(self.field_value)) <= int(self.args[0]) 163 | 164 | def get_message(self): 165 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, MAX=self.args[0]) 166 | 167 | 168 | class IDS(BaseRule): 169 | name = 'ids' 170 | message = _('{VALUE} of {FIELD} is not a id series') 171 | description = _('check it the given value is id string such as 1,2,3,4') 172 | regex = r'^\d+(?:,\d+)*$' 173 | 174 | def check_null(self): 175 | pass 176 | 177 | def check_value(self): 178 | self.status = True if re.match(self.regex, self.field_value) else False 179 | 180 | 181 | class Cellphone(BaseRule): 182 | # TODO fix different cellphone formats in different countries 183 | name = 'cellphone' 184 | regex = r'^([\+]?[0-9]{2})?1[0-9]{10}$' 185 | message = _('{VALUE} of {FIELD} is not a cellphone number') 186 | description = _('check if the given value is a cellphone number , ' 187 | 'if there is a internation code it sould begin with + .') 188 | 189 | def check_null(self): 190 | pass 191 | 192 | def check_value(self): 193 | self.status = True if re.match(self.regex, self.field_value) else False 194 | 195 | 196 | class Regex(BaseRule): 197 | name = 'regex' 198 | message = _('{VALUE} of {FIELD} is not mathc the pattern {REGEX}') 199 | description = _('check the given value if suits the regex') 200 | parse_args = False 201 | 202 | def check_null(self): 203 | pass 204 | 205 | def check_value(self): 206 | self.status = True if self._match() else False 207 | 208 | def get_message(self): 209 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, REGEX=self._get_regex()) 210 | 211 | def _get_regex(self): 212 | return self.args 213 | 214 | def _match(self): 215 | return re.match(self._get_regex(), str(self.field_value)) 216 | 217 | 218 | class Email(BaseRule): 219 | name = 'email' 220 | message = _('{VALUE} of {FIELD} is not an email address') 221 | description = _('check for email addresses') 222 | pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' 223 | 224 | def check_null(self): 225 | pass 226 | 227 | def check_value(self): 228 | self.status = True if re.match(self.pattern, self.field_value) else False 229 | 230 | 231 | class Digits(BaseRule): 232 | # TODO add length control in digits rule 233 | name = 'digits' 234 | message = _('{VALUE} of {FIELD} is not match digits') 235 | description = _('check if the given value is made of digits') 236 | 237 | def check_null(self): 238 | pass 239 | 240 | def check_value(self): 241 | digits_regex = r'[0-9]+' 242 | self.status = True if re.fullmatch(digits_regex, str(self.field_value)) else False 243 | 244 | 245 | class Numberic(BaseRule): 246 | name = 'numberic' 247 | message = _('{VALUE} of {FIELD} is not match numberic') 248 | description = _('check if the given value is a integer number') 249 | 250 | def check_null(self): 251 | pass 252 | 253 | def check_value(self): 254 | regex = r'^[1-9]{1}[0-9]*' 255 | self.status = True if re.fullmatch(regex, str(self.field_value)) else False 256 | 257 | 258 | class ActiveURL(BaseRule): 259 | name = 'active_url' 260 | message = '{VALUE} of {FIELD} field is not a active URL' 261 | description = _('check if the given value if an active url you can visit.') 262 | 263 | def check_null(self): 264 | pass 265 | 266 | def check_value(self): 267 | try: 268 | socket.gethostbyname(self.field_value) 269 | except socket.gaierror: 270 | self.status = False 271 | 272 | 273 | class Date(BaseRule): 274 | name = 'date' 275 | message = _('{VALUE} of {FIELD} field is not a valid date format as {FORMAT_STR}') 276 | format_str = '%Y-%m-%d' 277 | description = _('check the given value if suits the date format of format_str') 278 | 279 | def get_format(self): 280 | return self.args[0] if 1 == len(self.args) and self.args[0] else self.format_str 281 | 282 | def check_null(self): 283 | pass 284 | 285 | def check_value(self): 286 | date_format = self.get_format() 287 | try: 288 | datetime.datetime.strptime(self.field_value, date_format) 289 | except ValueError: 290 | self.status = False 291 | 292 | def get_message(self): 293 | format_str = self.get_format() 294 | return self.message.format(FIELD=self.field_name, 295 | VALUE=self.field_value, 296 | FORMAT_STR=format_str, 297 | RULE_NAME=self.name) 298 | 299 | 300 | class Datetime(BaseRule): 301 | name = 'datetime' 302 | message = _('{VALUE} of {FIELD} field is not a valid datetime format as {FORMAT_STR}') 303 | format_str = '%Y-%m-%d %H:%M:%S' 304 | description = _('check the given value if suits the date time format of format_str') 305 | 306 | def get_format(self): 307 | return self.args[0] if 1 == len(self.args) and self.args[0] else self.format_str 308 | 309 | def check_null(self): 310 | pass 311 | 312 | def check_value(self): 313 | datetime_format = self.get_format() 314 | try: 315 | datetime.datetime.strptime(self.field_value, datetime_format) 316 | except ValueError: 317 | self.status = False 318 | 319 | def get_message(self): 320 | format_str = self.get_format() 321 | return self.message.format(FIELD=self.field_name, 322 | VALUE=self.field_value, 323 | FORMAT_STR=format_str, 324 | RULE_NAME=self.name) 325 | 326 | 327 | class DateBefore(BaseRule): 328 | name = 'date_before' 329 | message = _('{VALUE} of {FIELD} is not before date {DATE}') 330 | field_format_str = '%Y-%m-%d' 331 | param_format_str = '%Y-%m-%d' 332 | description = _('check the given value if before the date time format of format_str') 333 | 334 | def check_null(self): 335 | pass 336 | 337 | def check_value(self): 338 | param_date = self._get_param_date() 339 | field_date = self._get_field_date() 340 | self.status = field_date < param_date 341 | 342 | def _get_param_date(self): 343 | date_str = self.get_arg(0) 344 | 345 | if not date_str: 346 | raise RuleMissedParameterError(_('DateBefore Rule missed a paramter')) 347 | 348 | param_format_str = self.get_arg(1) if self.get_arg(1) else self.param_format_str 349 | date = datetime.datetime.strptime(date_str, param_format_str) 350 | return date 351 | 352 | def _get_field_date(self): 353 | field_format_str = self.get_arg(2) if self.get_arg(2) else self.field_format_str 354 | date = datetime.datetime.strptime(self.field_value, field_format_str) 355 | return date 356 | 357 | def get_message(self): 358 | return self.message.format(VALUE=self.field_value, 359 | FIELD=self.field_name, 360 | DATE=self.args[0]) 361 | 362 | 363 | class DateAfter(BaseRule): 364 | name = 'date_after' 365 | message = _('{VALUE} of {FIELD} is not after date {DATE}') 366 | field_format_str = '%Y-%m-%d' 367 | param_format_str = '%Y-%m-%d' 368 | description = _('check the given value if after the date time format of format_str') 369 | 370 | def check_null(self): 371 | pass 372 | 373 | def check_value(self): 374 | param_date = self._get_param_date() 375 | field_date = self._get_field_date() 376 | self.status = field_date > param_date 377 | 378 | def _get_param_date(self): 379 | date_str = self.args[0] 380 | 381 | if not date_str: 382 | raise RuleMissedParameterError('date_after missed a parameter') 383 | 384 | param_format_str = self.get_arg(1) if self.get_arg(1) else self.param_format_str 385 | date = datetime.datetime.strptime(date_str, param_format_str) 386 | return date 387 | 388 | def _get_field_date(self): 389 | field_format_str = self.get_arg(2) if self.get_arg(2) else self.field_format_str 390 | date = datetime.datetime.strptime(self.field_value, field_format_str) 391 | return date 392 | 393 | def get_message(self): 394 | return self.message.format(VALUE=self.field_value, 395 | FIELD=self.field_name, 396 | DATE=self.args[0]) 397 | 398 | 399 | class DateRange(BaseRule): 400 | name = 'date_range' 401 | message = _('{VALUE} of {FIELD} is not in range date of {BEGIN} to {END}') 402 | field_format_str = '%Y-%m-%d' 403 | param_format_str = '%Y-%m-%d' 404 | description = _('check the given value if between the param date string') 405 | 406 | def check_null(self): 407 | pass 408 | 409 | def check_value(self): 410 | begin, end = self._get_param_date() 411 | date = self._get_field_date() 412 | self.status = begin < date < end 413 | 414 | def _get_param_date(self): 415 | begin_date_str = self.args[0] 416 | begin_date = datetime.datetime.strptime(begin_date_str, self.param_format_str) 417 | 418 | end_date_str = self.args[1] 419 | end_date = datetime.datetime.strptime(end_date_str, self.param_format_str) 420 | 421 | return begin_date, end_date 422 | 423 | def _get_field_date(self): 424 | date = datetime.datetime.strptime(self.field_value, self.field_format_str) 425 | return date 426 | 427 | def get_message(self): 428 | return self.message.format(VALUE=self.field_value, 429 | FIELD=self.field_name, 430 | BEGIN=self.args[0], 431 | END=self.args[1]) 432 | 433 | 434 | class DatetimeBefore(BaseRule): 435 | name = 'datetime_before' 436 | message = _('{VALUE} of {FIELD} is not before {DATETIME}') 437 | field_format_str = '%Y-%m-%d %H:%M:%S' 438 | param_format_str = '%Y-%m-%d %H:%M:%S' 439 | description = _('check the given value if before the datetime format of format_str') 440 | 441 | def check_null(self): 442 | pass 443 | 444 | def check_value(self): 445 | field_datetime = self._get_field_datetime() 446 | param_datetime = self._get_param_datetime() 447 | self.status = field_datetime < param_datetime 448 | 449 | def get_message(self): 450 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, DATETIME=self.args[0]) 451 | 452 | def _get_field_datetime(self): 453 | return datetime.datetime.strptime(self.field_value, self.field_format_str) 454 | 455 | def _get_param_datetime(self): 456 | datetime_str = self.args[0] if len(self.args) == 1 else None 457 | return datetime.datetime.strptime(datetime_str, self.param_format_str) 458 | 459 | 460 | class DatetimeRange(BaseRule): 461 | name = 'datetime_range' 462 | message = _('{VALUE} of {FIELD} is not in range of {BEGIN} to {END}') 463 | field_format_str = '%Y-%m-%d %H:%M:%S' 464 | param_format_str = '%Y-%m-%d %H:%M:%S' 465 | description = _('check the given value if between the datetime format of the param datetime') 466 | 467 | def check_null(self): 468 | pass 469 | 470 | def check_value(self): 471 | field_datetime = self._get_field_datetime() 472 | begin, end = self._get_param_datetime() 473 | self.status = end > field_datetime > begin 474 | 475 | def get_message(self): 476 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, BEGIN=self.args[0], END=self.args[1]) 477 | 478 | def _get_field_datetime(self): 479 | return datetime.datetime.strptime(self.field_value, self.field_format_str) 480 | 481 | def _get_param_datetime(self): 482 | begin_datetime_str = self.args[0] if len(self.args) == 2 else None 483 | begin = datetime.datetime.strptime(begin_datetime_str, self.param_format_str) 484 | 485 | end_datetime_str = self.args[1] if len(self.args) == 2 else None 486 | end = datetime.datetime.strptime(end_datetime_str, self.param_format_str) 487 | 488 | return begin, end 489 | 490 | 491 | class DatetimeAfter(BaseRule): 492 | name = 'datetime_after' 493 | message = _('{VALUE} of {FIELD} is not in after {DATETIME}') 494 | field_format_str = '%Y-%m-%d %H:%M:%S' 495 | param_format_str = '%Y-%m-%d %H:%M:%S' 496 | description = _('check the given value if after the datetime format of format_str') 497 | 498 | def check_null(self): 499 | pass 500 | 501 | def check_value(self): 502 | field_datetime = self._get_field_datetime() 503 | param_datetime = self._get_param_datetime() 504 | self.status = field_datetime > param_datetime 505 | 506 | def get_message(self): 507 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, DATETIME=self.args[0]) 508 | 509 | def _get_field_datetime(self): 510 | return datetime.datetime.strptime(self.field_value, self.field_format_str) 511 | 512 | def _get_param_datetime(self): 513 | datetime_str = self.args[0] if len(self.args) == 1 else None 514 | return datetime.datetime.strptime(datetime_str, self.param_format_str) 515 | 516 | 517 | class Unblank(BaseRule): 518 | name = 'unblank' 519 | message = _('{FIELD} field is should not be blank') 520 | description = _('the given field should not be blank') 521 | 522 | def check_null(self): 523 | pass 524 | 525 | def check_value(self): 526 | try: 527 | value_str = str(self.field_value).strip() 528 | except ValueError: 529 | value_str = '' 530 | 531 | self.status = True if value_str else False 532 | 533 | 534 | class Required(BaseRule): 535 | name = 'required' 536 | message = _('{FIELD} field is required') 537 | description = _('the given field is required') 538 | 539 | def check_null(self): 540 | self.status = False 541 | 542 | def check_value(self): 543 | try: 544 | value_str = str(self.field_value).strip() 545 | except ValueError: 546 | value_str = '' 547 | 548 | self.status = True if value_str else False 549 | 550 | 551 | class Accepted(BaseRule): 552 | name = 'accepted' 553 | message = _('{VALUE} of {FIELD} field must in which of : {FLAGS}') 554 | flag = ['yes', 'no', 'true', 'false', '0', '1'] 555 | description = _('the given field must in the flag array') 556 | 557 | def get_flag_str(self): 558 | return ', '.join(self.flag) 559 | 560 | def check_null(self): 561 | pass 562 | 563 | def check_value(self): 564 | self.status = self.check_flag() 565 | 566 | def check_flag(self): 567 | flag = self.field_value.lower() 568 | return flag in self.flag or flag in list(self.args) 569 | 570 | def get_message(self): 571 | return self.message.format(FIELD=self.field_name, 572 | VALUE=self.field_value, 573 | FLAGS=self.get_flag_str(), 574 | RULE_NAME=self.name) 575 | 576 | 577 | class Unique(BaseRule): 578 | name = 'unique' 579 | message = _('{VALUE} of {MODEL} with {MODEL_FIELD} is not unique') 580 | description = _('the given value must unique of the table') 581 | 582 | def check_null(self): 583 | pass 584 | 585 | def check_value(self): 586 | self.status = self.check_model() 587 | 588 | def check_model(self): 589 | model_name, model_field = self.args 590 | model = self.get_model(model_name) 591 | qs = model.objects.filter(**{model_field: self.field_value}) 592 | return not qs.exists() 593 | 594 | @staticmethod 595 | def get_model(name): 596 | from django.conf import settings 597 | from django.apps import apps 598 | 599 | if 'AUTH_USER_MODEL' == name: 600 | app, name = settings.AUTH_USER_MODEL.split('.') 601 | else: 602 | app, name = name.split('.') 603 | return apps.get_model(app, name) 604 | 605 | def get_message(self): 606 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, 607 | MODEL=self.args[0], 608 | MODEL_FIELD=self.args[1]) 609 | 610 | 611 | class AlphaDash(BaseRule): 612 | name = 'alpha_dash' 613 | message = _('{VALUE} is invalid alpha dash format string.') 614 | regex = '[a-zA-Z-_]+' 615 | description = _('The field under validation may have alpha-numeric characters, as well as dashes and underscores.') 616 | 617 | def check_value(self): 618 | self.status = re.match(self.regex, self.field_value) 619 | 620 | def check_null(self): 621 | pass 622 | 623 | 624 | class AlphaNumber(BaseRule): 625 | name = 'alpha_number' 626 | message = _('{VALUE} is not a alpha-number string.') 627 | regex = '[a-zA-Z0-9]+' 628 | description = _('the given value must conbines with only alphabets and numbers ') 629 | 630 | def check_value(self): 631 | self.status = re.match(self.regex, self.field_value) 632 | 633 | def check_null(self): 634 | pass 635 | 636 | 637 | class Array(BaseRule): 638 | name = 'array' 639 | message = _('{VALUE} is not a comma splited string') 640 | description = _('the given must be a comma splited string.') 641 | 642 | def check_value(self): 643 | self.status = True if len(self.field_value.split(',')) > 2 else False 644 | 645 | def check_null(self): 646 | pass 647 | 648 | 649 | class DateBeforeEqual(BaseRule): 650 | name = 'date_before_equal' 651 | message = _('{VALUE} of {FIELD} is not before or equal date {DATE}') 652 | field_format_str = '%Y-%m-%d' 653 | param_format_str = '%Y-%m-%d' 654 | description = _('check the given value if before or equal the date time format of format_str') 655 | 656 | def check_null(self): 657 | pass 658 | 659 | def check_value(self): 660 | param_date = self._get_param_date() 661 | field_date = self._get_field_date() 662 | self.status = field_date <= param_date 663 | 664 | def _get_param_date(self): 665 | date_str = self.get_arg(0) 666 | 667 | if not date_str: 668 | raise RuleMissedParameterError(_('DateBefore Rule missed a paramter')) 669 | 670 | param_format_str = self.get_arg(1) if self.get_arg(1) else self.param_format_str 671 | date = datetime.datetime.strptime(date_str, param_format_str) 672 | return date 673 | 674 | def _get_field_date(self): 675 | field_format_str = self.get_arg(2) if self.get_arg(2) else self.field_format_str 676 | date = datetime.datetime.strptime(self.field_value, field_format_str) 677 | return date 678 | 679 | def get_message(self): 680 | return self.message.format(VALUE=self.field_value, 681 | FIELD=self.field_name, 682 | DATE=self.args[0]) 683 | 684 | 685 | class DateAfterEqual(BaseRule): 686 | name = 'date_after_equal' 687 | message = _('{VALUE} of {FIELD} is not after or equal date {DATE}') 688 | field_format_str = '%Y-%m-%d' 689 | param_format_str = '%Y-%m-%d' 690 | description = _('check the given value if after or equal the date time format of format_str') 691 | 692 | def check_null(self): 693 | pass 694 | 695 | def check_value(self): 696 | param_date = self._get_param_date() 697 | field_date = self._get_field_date() 698 | self.status = field_date >= param_date 699 | 700 | def _get_param_date(self): 701 | date_str = self.args[0] 702 | 703 | if not date_str: 704 | raise RuleMissedParameterError('date_after missed a parameter') 705 | 706 | param_format_str = self.get_arg(1) if self.get_arg(1) else self.param_format_str 707 | date = datetime.datetime.strptime(date_str, param_format_str) 708 | return date 709 | 710 | def _get_field_date(self): 711 | field_format_str = self.get_arg(2) if self.get_arg(2) else self.field_format_str 712 | date = datetime.datetime.strptime(self.field_value, field_format_str) 713 | return date 714 | 715 | def get_message(self): 716 | return self.message.format(VALUE=self.field_value, 717 | FIELD=self.field_name, 718 | DATE=self.args[0]) 719 | 720 | 721 | class DateTimeBeforeEqual(BaseRule): 722 | name = 'datetime_before_equal' 723 | message = _('{VALUE} of {FIELD} is not before or equal {DATETIME}') 724 | field_format_str = '%Y-%m-%d %H:%M:%S' 725 | param_format_str = '%Y-%m-%d %H:%M:%S' 726 | description = _('check the given value if before or equal the datetime format of format_str') 727 | 728 | def check_null(self): 729 | pass 730 | 731 | def check_value(self): 732 | field_datetime = self._get_field_datetime() 733 | param_datetime = self._get_param_datetime() 734 | self.status = field_datetime <= param_datetime 735 | 736 | def get_message(self): 737 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, DATETIME=self.args[0]) 738 | 739 | def _get_field_datetime(self): 740 | return datetime.datetime.strptime(self.field_value, self.field_format_str) 741 | 742 | def _get_param_datetime(self): 743 | datetime_str = self.args[0] if len(self.args) == 1 else None 744 | return datetime.datetime.strptime(datetime_str, self.param_format_str) 745 | 746 | 747 | class DatetimeAfterEqual(BaseRule): 748 | name = 'datetime_after_equal' 749 | message = _('{VALUE} of {FIELD} is not after or equal {DATETIME}') 750 | field_format_str = '%Y-%m-%d %H:%M:%S' 751 | param_format_str = '%Y-%m-%d %H:%M:%S' 752 | description = _('check the given value if after or equal the datetime format of format_str') 753 | 754 | def check_null(self): 755 | pass 756 | 757 | def check_value(self): 758 | field_datetime = self._get_field_datetime() 759 | param_datetime = self._get_param_datetime() 760 | self.status = field_datetime >= param_datetime 761 | 762 | def get_message(self): 763 | return self.message.format(VALUE=self.field_value, FIELD=self.field_name, DATETIME=self.args[0]) 764 | 765 | def _get_field_datetime(self): 766 | return datetime.datetime.strptime(self.field_value, self.field_format_str) 767 | 768 | def _get_param_datetime(self): 769 | datetime_str = self.args[0] if len(self.args) == 1 else None 770 | return datetime.datetime.strptime(datetime_str, self.param_format_str) 771 | 772 | 773 | class Between(BaseRule): 774 | name = 'between' 775 | message = _('{VALUE} is not between of the {START} -> {END}') 776 | description = _('check the given value if between the params, it\'s only adapted for integer and string value') 777 | 778 | def check_value(self): 779 | start, stop = self._get_params() 780 | length = self.get_value_length() 781 | self.status = start <= length <= stop 782 | 783 | def get_value_length(self): 784 | try: 785 | length = int(self.field_value) 786 | except ValueError: 787 | length = len(self.field_value) 788 | 789 | return length 790 | 791 | def check_null(self): 792 | pass 793 | 794 | def get_message(self): 795 | start, stop = self._get_params() 796 | start, stop = stop, start if start > stop else None 797 | return self.message.format(VALUE=self.field_value, START=start, STOP=stop) 798 | 799 | def _get_params(self): 800 | if len(self.args) < 2: 801 | raise InvalidRuleParamterError(_('between rule needs 2 params.')) 802 | 803 | else: 804 | start = int(self.args[0]) 805 | stop = int(self.args[1]) 806 | return start, stop 807 | 808 | 809 | class Boolean(BaseRule): 810 | name = 'boolean' 811 | message = _('{VALUE} can not covert to boolean type') 812 | description = _('check the given value if boolean type') 813 | type_ = ['0', '1', 'true', 'false'] 814 | 815 | def check_value(self): 816 | self.status = True if self.field_value.strip().lower() in self.type_ else False 817 | 818 | def check_null(self): 819 | pass 820 | 821 | 822 | class FileRuleMixin: 823 | def check_value(self): 824 | self._check_file() 825 | 826 | def check_null(self): 827 | pass 828 | 829 | def _check_file(self): 830 | ext = self._get_ext() 831 | exts = self._get_exts() 832 | self.status = ext in exts 833 | 834 | def _check_ext(self): 835 | ext = self._get_ext() 836 | exts = self._get_exts() 837 | return ext in exts 838 | 839 | def _get_ext(self): 840 | name = self.field_value.name 841 | ext = name.split('.')[-1] 842 | return ext 843 | 844 | def _get_exts(self): 845 | return self.exts 846 | 847 | def get_message(self): 848 | file_name = self.field_value.name 849 | return self.message.format(FILE_NAME=file_name) 850 | 851 | 852 | class File(FileRuleMixin, BaseRule): 853 | name = 'file' 854 | message = _('{FILE_NAME} is not allowed to upload') 855 | description = _('check the uploaded file ext if allowed to upload this kind of file. ') 856 | 857 | def _get_exts(self): 858 | return self.args 859 | 860 | 861 | class Image(File): 862 | name = 'image' 863 | exts = ['png', 'jpeg', 'gif', 'jpg', 'svg'] 864 | 865 | 866 | class Video(File): 867 | name = 'video' 868 | exts = ['mp4', 'avi', 'mkv', 'flv', 'rmvb'] 869 | 870 | 871 | class Audio(File): 872 | name = 'audio' 873 | exts = ['mp3', 'wma', 'flac', 'ape', 'ogg'] 874 | 875 | 876 | class Attachement(File): 877 | name = 'attachement' 878 | exts = ['doc', 'zip', 'ppt', 'docx', 'excel', 'rar'] 879 | 880 | 881 | class SizeMixin: 882 | types = ['string', 'number', 'array', 'file'] 883 | 884 | def check_value(self): 885 | _type = self.get_arg(0) 886 | _size = self.get_arg(1) 887 | 888 | if _type and _size and _type in self.types: 889 | size = self._get_field_size(_type) 890 | self._check_size(float(_size), float(size)) 891 | else: 892 | raise InvalidRuleParamterError(_('invalid rule paramters')) 893 | 894 | def check_null(self): 895 | pass 896 | 897 | def _check_size(self, *args, **kwargs): 898 | raise NotImplementedError() 899 | 900 | def _get_field_size(self, _type): 901 | if 'string' == _type: 902 | return self._get_str_size() 903 | 904 | if 'number' == _type: 905 | return self._get_number_size() 906 | 907 | if 'array' == _type: 908 | return self._get_array_size() 909 | 910 | if 'file' == _type: 911 | return self._get_file_size() 912 | 913 | raise InvalidRuleParamterError(_('invalid rule parameters')) 914 | 915 | def _get_str_size(self): 916 | _value = str(self.field_value) 917 | return len(_value) 918 | 919 | def _get_number_size(self): 920 | _value = float(self.field_value) 921 | return _value 922 | 923 | def _get_file_size(self): 924 | size = self.field_value.size 925 | return size / 1000 926 | 927 | def _get_array_size(self): 928 | _value = len(self.field_value.split(',')) 929 | return _value 930 | 931 | def get_message(self): 932 | _type = self.get_arg(0) 933 | size = self._get_field_size(_type) 934 | return self.message.format(FIELD=self.field_name, SIZE=size) 935 | 936 | 937 | class Min(SizeMixin, BaseRule): 938 | name = 'min' 939 | message = _('size of {FIELD} is larger than {SIZE}') 940 | description = _('') 941 | 942 | def _check_size(self, _size, size): 943 | self.status = _size <= size 944 | 945 | 946 | class Max(SizeMixin, BaseRule): 947 | name = 'max' 948 | message = _('size of {FIELD} is smaller than {SIZE}') 949 | description = _('') 950 | 951 | def _check_size(self, _size, size): 952 | self.status = _size >= size 953 | 954 | 955 | class Size(SizeMixin, BaseRule): 956 | name = 'size' 957 | message = _('size of {FIELD} is not equals to {SIZE}') 958 | description = _('The field under validation must have a size matching the given value. ' 959 | 'For string data, value corresponds to the number of characters. ' 960 | 'For numeric data, value corresponds to a given integer value. ' 961 | 'For an array, size corresponds to the count of the array. ' 962 | 'For files, size corresponds to the file size in kilobytes.') 963 | 964 | def _check_size(self, _size, size): 965 | self.status = _size == size 966 | 967 | 968 | class Username(BaseRule): 969 | name = 'username' 970 | message = _('the input {VALUE} is not a proper username.') 971 | description = _('this rule will check the normal username, the initial of username must be a alphabet character and' 972 | 'it could conbimes with digits, dot, underscore and dash.') 973 | 974 | regex = r'^[a-z]{1}[a-z0-9\.\-_]*$' 975 | 976 | def check_value(self): 977 | self.status = True if re.fullmatch(self.regex, self.field_value) else False 978 | 979 | def check_null(self): 980 | pass 981 | 982 | 983 | class Password(BaseRule): 984 | name = 'password' 985 | message = _('the input is not a proper password.') 986 | description = _('a simple password validate rule , it has 3 level strength rule for password,' 987 | 'the simple rule just needs the password length has more than 7 simple ' 988 | 'characters includes digits number and alphabet characters' 989 | 'the middle rule needs the password has UPPER case characters , ' 990 | 'lower case characters, and digits numbers' 991 | 'the high rule needs the password combines with special ' 992 | 'characters, and UPPER case' 993 | 'characters and lowe case chracters, and digits numbers.') 994 | 995 | digits = 48, 57 996 | latin_upper = 65, 96 997 | latin_lower = 97, 122 998 | special = (33, 47), (58, 64), (123, 126) 999 | 1000 | level = ['low', 'middle', 'high'] 1001 | 1002 | def check_value(self): 1003 | level = self.get_level() 1004 | if 'low' == level: 1005 | self.status = self.check_low() 1006 | elif 'middle' == level: 1007 | self.status = self.check_middle() 1008 | elif 'high' == level: 1009 | self.status = self.check_high() 1010 | 1011 | def check_null(self): 1012 | pass 1013 | 1014 | def get_level(self): 1015 | level = self.args[0] 1016 | if level not in self.level: 1017 | raise InvalidRuleParamterError('parameters must be one of : low, middle, high') 1018 | else: 1019 | return level 1020 | 1021 | def check_low(self): 1022 | return len(self.field_value) >= 7 1023 | 1024 | def check_middle(self): 1025 | return len(self.field_value) >= 7 and \ 1026 | self.check_latin_lower() and \ 1027 | self.check_latin_upper() and \ 1028 | self.check_digits() 1029 | 1030 | def check_high(self): 1031 | return len(self.field_value) >= 7 and \ 1032 | self.check_latin_lower() and \ 1033 | self.check_latin_lower() and \ 1034 | self.check_digits() and \ 1035 | self.check_special() 1036 | 1037 | def check_digits(self, number=1): 1038 | seq = self.parse_value() 1039 | count = 0 1040 | for c in seq: 1041 | if self.digits[0] <= c <= self.digits[1]: 1042 | count = count + 1 1043 | 1044 | return count >= number 1045 | 1046 | def check_latin_lower(self, number=1): 1047 | seq = self.parse_value() 1048 | count = 0 1049 | for c in seq: 1050 | if self.latin_lower[0] <= c <= self.latin_lower[1]: 1051 | count = count + 1 1052 | return count >= number 1053 | 1054 | def check_latin_upper(self, number=1): 1055 | seq = self.parse_value() 1056 | count = 0 1057 | for c in seq: 1058 | if self.latin_upper[0] <= c <= self.latin_upper[1]: 1059 | count = count + 1 1060 | return count >= number 1061 | 1062 | def check_special(self, number=1): 1063 | seq = self.parse_value() 1064 | count = 0 1065 | for c in seq: 1066 | if self.special[0][0] <= c <= self.special[0][1] or \ 1067 | self.special[1][0] <= c <= self.special[1][1] or \ 1068 | self.special[2][0] <= c <= self.special[2][1]: 1069 | count = count + 1 1070 | return count >= number 1071 | 1072 | def parse_value(self): 1073 | ret = map(lambda d: ord(d), ','.join(self.field_value).split(',')) 1074 | return list(ret) 1075 | 1076 | 1077 | class ASCII(BaseRule): 1078 | name = 'ascii' 1079 | message = _('the input {VALUE} value is not a proper ASCII character.') 1080 | description = _('check the given value if is a ascii character series.') 1081 | 1082 | def check_value(self): 1083 | self.status = self.check_ascii() 1084 | 1085 | def check_null(self): 1086 | pass 1087 | 1088 | def check_ascii(self): 1089 | seq = filter(lambda d: ord(d) > 127, ','.join(self.field_value).split(',')) 1090 | return False if list(seq) else True 1091 | 1092 | 1093 | class PrintableASCII(BaseRule): 1094 | name = 'pascii' 1095 | message = _('this input {VALUE} is not a proper printable ASCII character string.') 1096 | description = _('check the give string if it is a printable ASCII string only' 1097 | 'contains the characters from 32 to 255') 1098 | 1099 | def check_value(self): 1100 | no_blank = self.get_arg(0) 1101 | if no_blank: 1102 | self.status = self.check_string_no_blank() 1103 | else: 1104 | self.status = self.check_string() 1105 | 1106 | def check_null(self): 1107 | self.status = True 1108 | 1109 | def check_string(self): 1110 | if not self.field_value: 1111 | return False 1112 | 1113 | seq = filter(lambda d: ord(d) > 255, ','.join(self.field_value).split(',')) 1114 | return False if list(seq) else True 1115 | 1116 | def check_string_no_blank(self): 1117 | code_list = [ 1118 | *list(range(33, 126)), 1119 | 128, 1120 | *list(range(130, 140)), 1121 | 142, 1122 | *list(range(142, 156)), 1123 | *list(range(158, 159)), 1124 | *list(range(161, 172)), 1125 | *list(range(174, 255))] 1126 | 1127 | field_value = self.field_value.strip() 1128 | 1129 | if not field_value: 1130 | return False 1131 | else: 1132 | for ch in field_value: 1133 | if ord(ch) not in code_list: 1134 | return False 1135 | else: 1136 | return True 1137 | 1138 | 1139 | class Same(BaseRule): 1140 | name = 'same' 1141 | message = _('the input value is not same as the value of {PARAM_FIELD}') 1142 | description = _('') 1143 | 1144 | def check(self): 1145 | name = self.get_arg(0) 1146 | if name: 1147 | value = self.data.get(name, None) 1148 | self.status = str(value) == str(self.field_value) 1149 | else: 1150 | raise InvalidRuleParamterError(_('wrong paramter for the Same Rule.')) 1151 | 1152 | def check_null(self): 1153 | self.status = self.check() 1154 | 1155 | def check_value(self): 1156 | self.status = self.check() 1157 | 1158 | def get_message(self): 1159 | return self.message.format(FIELD=self.field_name, 1160 | VALUE=self.field_value, 1161 | RULE_NAME=self.name, 1162 | PARAM_FIELD=self.get_arg(0), 1163 | PARAM_VALUE=self.data.get(self.get_arg(0), None)) 1164 | 1165 | 1166 | class Decimal(BaseRule): 1167 | name = 'decimal' 1168 | message = _('the input value {VALUE} of {FIELD} is not a decimal format number') 1169 | description = _('') 1170 | 1171 | def check_decimal(self): 1172 | r = r'^([\+\-])?[0-9]+(\.[0-9]+)?$' 1173 | m = re.fullmatch(r, str(self.field_value)) 1174 | return True if m else False 1175 | 1176 | def check_null(self): 1177 | pass 1178 | 1179 | def check_value(self): 1180 | self.status = self.check_decimal() 1181 | 1182 | def get_message(self): 1183 | return self.message.format(FIELD=self.field_name, 1184 | VALUE=self.field_value) 1185 | 1186 | 1187 | class Exist(Unique): 1188 | name = 'exist' 1189 | message = _('{VALUE} of {MODEL} with {MODEL_FIELD} not existis in database') 1190 | description = _('the given value must exist in the table of the database') 1191 | 1192 | def check_model(self): 1193 | model_name, model_field = self.args 1194 | model = self.get_model(model_name) 1195 | try: 1196 | qs = model.objects.filter(**{model_field: self.field_value}) 1197 | except ValueError: 1198 | return False 1199 | else: 1200 | return qs.exists() 1201 | 1202 | 1203 | class UniqueAgainst(Unique): 1204 | name = 'unique_against' 1205 | message = _('the given {MODEL_NAME} record is exist against ' 1206 | 'the {MODEL_FIELD} column by {MODEL_VALUE} with value {VALUE}') 1207 | description = _('check the given record weather exists in the database against the given column value') 1208 | 1209 | def check_model(self): 1210 | model_name, model_field, model_value = self.args 1211 | model = self.get_model(model_name) 1212 | qs = model.objects.filter(**{model_field: self.field_value}).exclude(**{model_field: model_value}) 1213 | return not qs.exists() 1214 | 1215 | def get_message(self): 1216 | return self.message.format(MODEL_NAME=self.args[0], 1217 | MODEL_FIELD=self.args[1], 1218 | MODEL_VALUE=self.args[2], 1219 | VALUE=self.field_value) 1220 | 1221 | 1222 | # just for normal integer includes 0, positive integer and negative integer all takes decimal 1223 | class Integer(Regex): 1224 | name = 'integer' 1225 | message = 'the given value {VALUE} for {FIELD} field is not a proper decimal integer' 1226 | description = _('check the given value if fits the decimal integer') 1227 | parse_args = False 1228 | 1229 | def _get_regex(self): 1230 | pattern = r'^[+-]*[1-9]+[0-9]*$' 1231 | return pattern 1232 | 1233 | 1234 | class PositiveInteger(Regex): 1235 | name = 'pos_integer' 1236 | message = 'the given value {VALUE} for {FIELD} field is not a proper positive decimal integer' 1237 | description = 'check the given value if fits the positive decimal integer' 1238 | 1239 | def _get_regex(self): 1240 | pattern = r'^[+]*[1-9]+[0-9]*$' 1241 | return pattern 1242 | 1243 | 1244 | class NegativeInteger(Regex): 1245 | name = 'neg_integer' 1246 | message = 'the given value {VALUE} for {FIELD} field is not a proper negative decimal integer' 1247 | description = 'check the given value if fits the negative decimal integer' 1248 | 1249 | def _get_regex(self): 1250 | pattern = r'^[-]+[1-9]+[0-9]*$' 1251 | return pattern 1252 | 1253 | 1254 | class Percentage(BaseRule): 1255 | name = 'percentage' 1256 | message = 'the give value {VALUE} is for {FIELD} field is not a positive integer from 0 to 100' 1257 | description = 'the give value must be a integer from 0 to 100' 1258 | 1259 | def check_null(self): 1260 | pass 1261 | 1262 | def check_value(self): 1263 | try: 1264 | value = int(self.field_value) 1265 | except ValueError: 1266 | self.status = False 1267 | else: 1268 | self.status = True if 0 <= value <= 100 else False 1269 | 1270 | 1271 | class IPAddress(BaseRule): 1272 | name = 'ip_address' 1273 | message = 'the given value {VALUE} for {FIELD} field is not a ipv4 or v6 address' 1274 | description = _('check the given value if it is a proper ipv4 or v6 address') 1275 | 1276 | def check_null(self): 1277 | pass 1278 | 1279 | def check_value(self): 1280 | import ipaddress 1281 | try: 1282 | ipaddress.ip_address(self.field_value) 1283 | except ValueError: 1284 | self.status = False 1285 | else: 1286 | self.status = True 1287 | 1288 | 1289 | class MetaValidator(type): 1290 | def __new__(mcs, *args, **kwargs): 1291 | name, base, attrs = args 1292 | attrs.update({'validation': mcs.get_attrs(attrs)}) 1293 | return super().__new__(mcs, *args) 1294 | 1295 | @staticmethod 1296 | def get_attrs(attrs): 1297 | return dict((k, v) for k, v in attrs.items() if not k.startswith('__') and isinstance(v, str)) 1298 | 1299 | 1300 | class Validator(metaclass=MetaValidator): 1301 | 1302 | def __init__(self, data, request=None, extra_rules=None): 1303 | self.data = deepcopy(data) 1304 | self.request = request 1305 | self.extra_rules = extra_rules 1306 | self.status = True 1307 | self.validate_message = {} 1308 | self.validate_message_plain = {} 1309 | 1310 | def validate(self): 1311 | validation = self._get_validation() 1312 | self._validate(validation) 1313 | return self.status 1314 | 1315 | def get(self, name, default=None): 1316 | return self.data.get(name, default) 1317 | 1318 | def get_status(self): 1319 | return self.status 1320 | 1321 | def get_message(self): 1322 | return self.validate_message 1323 | 1324 | def get_message_plain(self): 1325 | return self.validate_message_plain 1326 | 1327 | def set_message(self, name, rule, message): 1328 | self.validate_message.update({name: {}}) if name not in self.validate_message.keys() else None 1329 | self.validate_message[name].update({rule: message}) 1330 | 1331 | self.validate_message_plain.update({name: []}) if name not in self.validate_message_plain.keys() else None 1332 | self.validate_message_plain[name].append(message) 1333 | 1334 | def _validate(self, validation): 1335 | for item in validation: 1336 | name = item.get('name', '') 1337 | value = item.get('value', '') 1338 | rules = item.get('rules', []) 1339 | [self._check_rule(rule, name, value) for rule in rules] 1340 | 1341 | def _get_rule(self, rule_info, name, value): 1342 | rule_name = rule_info.get('name') 1343 | params = rule_info.get('params') 1344 | rule_class = self._get_origin_rule(rule_name) 1345 | message = getattr(self, 'message', {}).get(name, {}).get(rule_name, None) 1346 | instance = rule_class(name, value, params, data=self.data, message=message) 1347 | return instance 1348 | 1349 | def _get_origin_rule(self, name): 1350 | rule = self.extra_rules.get(name, None) if self.extra_rules else default_rules.get(name, None) 1351 | if not rule: 1352 | raise RuleNotFoundError(name) 1353 | else: 1354 | return rule 1355 | 1356 | def _check_rule(self, rule_info, name, value): 1357 | rule = self._get_rule(rule_info, name, value) 1358 | rule.check() 1359 | if not rule.get_status(): 1360 | self.status = False 1361 | self.set_message(name, rule_info.get('name'), rule.get_message()) 1362 | 1363 | def _get_validation(self): 1364 | ret = [] 1365 | for name, validation in self.validation.items(): 1366 | rules = self._get_rules(validation) 1367 | value = self.get(name) 1368 | data = {'name': name, 'value': value, 'rules': list(rules)} 1369 | ret.append(data) 1370 | return ret 1371 | 1372 | def _get_rules(self, validation): 1373 | rules = map(self._get_rule_info, validation.split('|')) 1374 | return rules 1375 | 1376 | @staticmethod 1377 | def _get_rule_info(rule): 1378 | info = list(map(lambda s: s.strip(), rule.split(':', 1))) 1379 | name = info[0] 1380 | params = info[1] if len(info) == 2 else '' 1381 | rules = {'name': name, 'params': params} 1382 | return rules 1383 | 1384 | 1385 | default_rules = { 1386 | Required.get_name(): Required, 1387 | Accepted.get_name(): Accepted, 1388 | Date.get_name(): Date, 1389 | DateBefore.get_name(): DateBefore, 1390 | DateAfter.get_name(): DateAfter, 1391 | DateRange.get_name(): DateRange, 1392 | Datetime.get_name(): Datetime, 1393 | DatetimeBefore.get_name(): DatetimeBefore, 1394 | DatetimeAfter.get_name(): DatetimeAfter, 1395 | DatetimeRange.get_name(): DatetimeRange, 1396 | ActiveURL.get_name(): ActiveURL, 1397 | Numberic.get_name(): Numberic, 1398 | Digits.get_name(): Digits, 1399 | Regex.get_name(): Regex, 1400 | Email.get_name(): Email, 1401 | MinLength.get_name(): MinLength, 1402 | MaxLength.get_name(): MaxLength, 1403 | IDS.get_name(): IDS, 1404 | Cellphone.get_name(): Cellphone, 1405 | Alphabet.get_name(): Alphabet, 1406 | Switch.get_name(): Switch, 1407 | Unique.get_name(): Unique, 1408 | Size.get_name(): Size, 1409 | Min.get_name(): Min, 1410 | Max.get_name(): Max, 1411 | File.get_name(): File, 1412 | Image.get_name(): Image, 1413 | Video.get_name(): Video, 1414 | Audio.get_name(): Audio, 1415 | Attachement.get_name(): Attachement, 1416 | AlphaDash.get_name(): AlphaDash, 1417 | AlphaNumber.get_name(): AlphaNumber, 1418 | Array.get_name(): Array, 1419 | DateBeforeEqual.get_name(): DateBeforeEqual, 1420 | DateAfterEqual.get_name(): DateAfterEqual, 1421 | DateTimeBeforeEqual.get_name(): DateTimeBeforeEqual, 1422 | DatetimeAfterEqual.get_name(): DatetimeAfterEqual, 1423 | Between.get_name(): Between, 1424 | Boolean.get_name(): Boolean, 1425 | Username.get_name(): Username, 1426 | Password.get_name(): Password, 1427 | ASCII.get_name(): ASCII, 1428 | Same.get_name(): Same, 1429 | Decimal.get_name(): Decimal, 1430 | Exist.get_name(): Exist, 1431 | UniqueAgainst.get_name(): UniqueAgainst, 1432 | PrintableASCII.get_name(): PrintableASCII, 1433 | Unblank.get_name(): Unblank, 1434 | Integer.get_name(): Integer, 1435 | PositiveInteger.get_name(): PositiveInteger, 1436 | NegativeInteger.get_name(): NegativeInteger, 1437 | IPAddress.get_name(): IPAddress, 1438 | Percentage.get_name(): Percentage 1439 | } 1440 | --------------------------------------------------------------------------------