{ "info": { "author": "Hiroaki Yamamoto", "author_email": "hiroaki@hysoftware.net", "bugtrack_url": null, "classifiers": [ "Framework :: Django :: 2.0", "License :: OSI Approved :: MIT License" ], "description": "# Additional code for Django\n\n[![CI Image]][CI Link] [![Test Coverage]][Test Coverage Link]\n[![Maintainability]][Maintainability Link]\n\n[CI Image]: https://circleci.com/gh/hiroaki-yamamoto/djextra.svg?style=svg\n[CI Link]: https://circleci.com/gh/hiroaki-yamamoto/djextra\n[Test Coverage]: https://api.codeclimate.com/v1/badges/1ed2f1c354e6357d711c/test_coverage\n[Test Coverage Link]: https://codeclimate.com/github/hiroaki-yamamoto/djextra/test_coverage\n[Maintainability]: https://api.codeclimate.com/v1/badges/1ed2f1c354e6357d711c/maintainability\n[Maintainability Link]: https://codeclimate.com/github/hiroaki-yamamoto/djextra/maintainability\n\n## What this?\nThis repository contains additional code for Django.\n\n## Why I create this?\nBecause I love Django, and usually using it. However, I found some essential\ncode was lacked for modern web development. For example, you might want to send\nAjax Payload like this:\n\n```JSON\n{\n \"name\": \"John Doe\",\n \"age\": 49,\n \"email\": \"john@example.com\",\n \"email_aliases\": [\n \"john.due@example.com\",\n \"due_49@example.com\",\n \"john.1968@example.com\"\n ]\n}\n```\n\nIn this case, you can validate name, age, and email field by using `Form`\nlayer on Django. However, email_aliases cannot be validated because it's a\nlist and it should validate each value whether it is email-formatted or not.\n\nTo support this case (and some other cases that Django can't handle), I wrote\nsome code to support List validation.\n\n## How To Use It\n\n### Forms\n\n#### Angular form\nAs you can see above sections, you'll need to implement redundant code:\n\n```Python\nfrom django import forms\nfrom .models import UserInfo\n\nclass UserInfoForm(forms.ModelForm):\n class Meta(object):\n model = UserInfo\n exclude = (\"2fa_secret\", )\n # They are already implemented because UserInfoForm inherit ModelForm\n # and the target model has the fields.\n widgets = {\n \"age\": forms.NumberInput(attrs={\"data-ng-model\": \"model.age\"}),\n \"phone\": forms.TextInput(attrs={\"data-ng-model\": \"model.phone\"}),\n \"street\": forms.TextInput(attrs={\"data-ng-model\": \"model.street\"}),\n \"city\": forms.TextInput(attrs={\"data-ng-model\": \"model.city\"}),\n \"state\": forms.TextInput(attrs={\"data-ng-model\": \"model.state\"})\n }\n```\n\nHowever, you can implement simpler code by using `AngularForm`:\n\n```Python\nfrom django import forms\nfrom djextra.forms.angular1 import AngularForm\n\nclass UserInfoForm(AngularForm, forms.ModelForm):\n ng_model_prefix = \"model\" # Change this if you want to use other than \"model\"\n class Meta(object):\n model = UserInfo\n exclude = (\"2fa_secret\", )\n # Automatically generates AngularJS forms.\n```\n\n##### Data binding between AngularJS and Django\nIf you want put the value to scope model on initialization, you might have 2 ways:\n\n1. Serialize your model into json by using `json.dumps` and\n `django.forms.model_to_dict`\n2. Set `handle_ng_init` meta attribute\n\nThe first one is very clear, convert your model into dict with\n`django.forms.model_to_dict`, and serialize the dict into JSON, and finally\nput the text as `data-ng-init` to the form like this:\n\n```HTML\n
\n```\n\nThe second one is simple; just set `handle_ng_init` Meta attribute of the form to\n`True` like this:\n\n```Python\nfrom django import forms\nfrom djextra.forms.angular1 import AngularForm\n\nclass UserInfoForm(AngularForm, forms.ModelForm):\n ng_model_prefix = \"model\" # Change this if you want to use other than \"model\"\n handle_ng_init = True\n class Meta(object):\n model = UserInfo\n exclude = (\"2fa_secret\", )\n # Automatically generates AngularJS forms.\n```\n\nIf you want to specify what value to be set, you can use `ng_init_format_func`\nmeta attribute like this:\n\n```Python\nfrom django import forms\nfrom djextra.forms.angular1 import AngularForm\n\nclass UserInfoForm(AngularForm, forms.ModelForm):\n ng_model_prefix = \"model\" # Change this if you want to use other than \"model\"\n handle_ng_init = True\n ng_init_format_func = {\n \"age\": lambda value: f\"{value} years old\"\n }\n class Meta(object):\n model = UserInfo\n exclude = (\"2fa_secret\", )\n # Automatically generates AngularJS forms.\n```\n\nHowever, as you know, server-side is quite different from client side, so **to\nkeep that `age` is formatted, you might also need to write client-side code.**\n\n#### All required forms\nIf you'd like to make all fields required on ModelForm, you will re-implement\nentire fields like this:\n\n```Python\nfrom django import forms\nfrom .models import UserInfo\n\nclass UserInfoForm(forms.ModelForm):\n class Meta(object):\n model = UserInfo\n exclude = (\"2fa_secret\", )\n\n # Assume that all fields are optional.\n age = forms.IntegerField(\n required=True,\n widget=forms.NumberInput(attrs={\"data-ng-model\": \"model.age\"})\n )\n phone = forms.CharField(\n required=True,\n widget=forms.TextInput(attrs={\"data-ng-model\": \"model.phone\"})\n )\n street = forms.CharField(\n required=True,\n widget=forms.TextInput(attrs={\"data-ng-model\": \"model.street\"})\n )\n city = forms.CharField(\n required=True,\n widget=forms.TextInput(attrs={\"data-ng-model\": \"model.city\"})\n )\n state = forms.CharField(\n required=True,\n widget=forms.TextInput(attrs={\"data-ng-model\": \"model.state\"})\n )\n```\n\nMoreover, you will not be able to check if the field is proper unless you\nrefer Django's code. To reduce this time consumption, I implemented\n`AllReqiuredForm`:\n\n```Python\nfrom django import forms\nfrom djextra.forms.angular1 import AllRequiredForm\nfrom .models import UserInfo\n\nclass UserInfoForm(AllRequiredForm, forms.ModelForm):\n class Meta(object):\n model = UserInfo\n exclude = (\"2fa_secret\", )\n # Assume that all fields are optional.\n```\n\nBy using `AllRequiredForm`, you can reduce your LOC like above. Of course,\nyou can put optional field as exceptions like this:\n\n```Python\nfrom django import forms\nfrom djextra.forms.angular1 import AllRequiredForm\nfrom .models import UserInfo\n\nclass UserInfoForm(AllRequiredForm, forms.ModelForm):\n class Meta(object):\n model = UserInfo\n exclude = (\"2fa_secret\", )\n # Assume that all fields are optional.\n # By specifying optional, the specified fields won't\n # become a required field.\n optional = (\"phone\", )\n```\n\n#### FieldAttributeForm\n\nWhen you set attribute, especially with `ModelForm`, you might need to re-set\nwidget with `widget` Meta attribute like this:\n\n```Python\nfrom django.db import models as db\nfrom django import forms\n\n\nclass NamePrice(db.Model):\n name = db.CharField()\n price = db.IntegerField()\n\n\nclass NameDescForm(forms.ModelForm):\n class Meta(object):\n model = NamePrice\n exclude = (\"id\", )\n widgets = {\n \"price\": forms.NumberInput(attrs={\"max\": \"100\"})\n }\n```\n\nThis is okay when you know what widget is used and attribute `max` is the\nfixed value. However, if you don't know what widget is used, or `max` is\nthe dynamic value by the server, Django might not have suitable solution.\nTo solve this problem, djextra has a form named `FieldAttributeForm` and\nyou can use it like this:\n\n```Python\nfrom django.db import models as db\nfrom django import forms\n\nfrom django.conf import settings\n\n\nclass NamePrice(db.Model):\n name = db.CharField()\n price = db.IntegerField()\n\n\nclass NameDescForm(FieldAttributeForm, forms.ModelForm):\n class Meta(object):\n model = NamePrice\n exclude = (\"id\", )\n fld_attrs = {\n \"price\": {\n # The point is the attribute can be callable.\n \"max\": lambda form, fld, name, value: 100 if value else \"\",\n \"min\": \"0\"\n },\n }\n```\n\nIn addition to this, `FieldAttributeForm` can set attributes that can be applied\nto all the fields by using `common_attrs` meta attribute:\n\n```Python\nfrom django.db import models as db\nfrom django import forms\n\nfrom django.conf import settings\n\n\nclass NamePrice(db.Model):\n name = db.CharField()\n price = db.IntegerField()\n\n\nclass NameDescForm(FieldAttributeForm, forms.ModelForm):\n class Meta(object):\n model = NamePrice\n exclude = (\"id\", )\n common_attrs = {\n # Also it can be callable.\n \"data-on-delay\": lambda form, fld, name, value: (\n f\"delay('{name}',{value})\"\n ),\n \"data-on-load\": \"test()\",\n }\n fld_attrs = {\n \"price\": {\n \"max\": lambda form, fld, name, value: 100 if value else \"\",\n \"min\": 0\n },\n }\n```\n\n### Form Fields\n\n#### ListField\n\nListField is used to handle a list of values like above example.\nTo use ListField, you can write a form like this:\n\n`forms.py`\n```python\nfrom django import forms\nfrom djextra import forms as exforms\n\n\nclass ExampleForm(forms.Form):\n name = forms.CharField()\n age = forms.IntegerField()\n email = forms.EmailField()\n email_aliases = exforms.ListField(field=forms.EmailField())\n```\n\nThen, Inputting the data as usual, the validation will start.\nIf you don't specify `field` keyword argument, `django.forms.CharField` object\nis specified.\n\n### Widgets\n\n#### Widgets for Angular Materials\n\nIf you like [Material Design], you'd also like to use [Angular Material], but\nas you can see the doc. the components are using special tags. For example,\n`select` and `option` input controllers should be replaced with `mdSelect` and\n`mdOption` and they are not provided by built-in widgets.\n\nThis widget provides the widgets:\n\n```Python\nfrom django import forms\nfrom djextra.forms.angular1 import (\n AngularForm, MDSelect, MDMultiSelect, MDDatePicker, MDDateSelect, MDCheckBox\n)\n\nfrom .models import ExampleModel\n\nclass ExampleForm(AngularForm, forms.ModelForm):\n class Meta(object):\n model = ExampleModel\n exclude = (\"secret_field\", )\n widgets = {\n \"start_since\": MDDateSelect(),\n \"available_date\": MDDatePicker(),\n \"shape\": MDSelect(choices=(\n (\"F\", \"Fat\"), (\"N\": \"Normal\"), (\"T\", \"Thin\")\n )),\n \"needs_fill\": MDCheckBox(\"Fill with border color?\")\n }\n```\n\n[Material Design]: https://material.google.com/\n[Angular Material]: https://material.angularjs.org\n\n\n## Contribution\nContribution of code is welcome, and the code is tested with tox. Before\nsending your pull request, please check you tested your code very well.\n\n## License\nThis repository is licensed under the terms of MIT License. Please check\n[LICENSE.md](LICENSE.md) for the detail.", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/hiroaki-yamamoto/djextra", "keywords": "Django", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "djextra", "package_url": "https://pypi.org/project/djextra/", "platform": "", "project_url": "https://pypi.org/project/djextra/", "project_urls": { "Homepage": "https://github.com/hiroaki-yamamoto/djextra" }, "release_url": "https://pypi.org/project/djextra/1.1.8/", "requires_dist": null, "requires_python": "", "summary": "Additional Functions for Django", "version": "1.1.8" }, "last_serial": 4692551, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "2e2d0dbfc2a9bbffb4c202d0ded7f122", "sha256": "a897df2923db3e60ddca7e58dbfb1c32c0f715efca2ebc8c2af5d887c16def9a" }, "downloads": -1, "filename": "djextra-0.1.tar.gz", "has_sig": false, "md5_digest": "2e2d0dbfc2a9bbffb4c202d0ded7f122", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 3727, "upload_time": "2017-12-24T03:24:21", "url": "https://files.pythonhosted.org/packages/4c/2f/0526b23cb5d5036cd5a82897fd755a67af5fd4ce4f15e9d5274d507db07d/djextra-0.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "8eb7f6038155d94ff75e6f75af7c2700", "sha256": "9582001b6bf06b2643c6b0248aa714f89fa0ba17652f90ff723fe0c4c0dab4c5" }, "downloads": -1, "filename": "djextra-1.0.0.tar.gz", "has_sig": false, "md5_digest": "8eb7f6038155d94ff75e6f75af7c2700", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 3742, "upload_time": "2017-12-24T03:28:50", "url": "https://files.pythonhosted.org/packages/50/d9/3912527e3dfdc8eaf0bdbb13d5bd2e3fdd352041360d85a09c89261a6a06/djextra-1.0.0.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "0b59ffddac477f8cb7888c9e9d3ced86", "sha256": "2fb84c3d03fb0dfc7581801ac817b30acc88ba2815a11e59dcda9e279b4ac366" }, "downloads": -1, "filename": "djextra-1.1.0.tar.gz", "has_sig": false, "md5_digest": "0b59ffddac477f8cb7888c9e9d3ced86", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14195, "upload_time": "2018-01-15T07:25:54", "url": "https://files.pythonhosted.org/packages/b5/34/4bc040e4582edf95ab4f69bdf0ab5fb2f996b5639819cf875100f5e55323/djextra-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "ca0c0b7fb0df11da2fe822d3666934e5", "sha256": "30e55b4202f6723390be79005a8222ae99efa6a5bcf17b7153d3ab77a8736b7a" }, "downloads": -1, "filename": "djextra-1.1.1.tar.gz", "has_sig": false, "md5_digest": "ca0c0b7fb0df11da2fe822d3666934e5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11231, "upload_time": "2018-01-15T07:36:09", "url": "https://files.pythonhosted.org/packages/22/f9/bc456e3e6d075438a08a848eb6743080b0b4a2f9e2639c47a00d492c00b7/djextra-1.1.1.tar.gz" } ], "1.1.2": [ { "comment_text": "", "digests": { "md5": "7088ea44b64d9ffc6095da1a73a4c33f", "sha256": "889f7d76852a3fdf99a3439395743b6ee26aa4491faadb48c0147ef621f3e215" }, "downloads": -1, "filename": "djextra-1.1.2.tar.gz", "has_sig": false, "md5_digest": "7088ea44b64d9ffc6095da1a73a4c33f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11251, "upload_time": "2018-01-19T06:46:52", "url": "https://files.pythonhosted.org/packages/29/f1/6effcfae950f52e7f0243d7c9a382ed838c43729ee1a61a4704575523db2/djextra-1.1.2.tar.gz" } ], "1.1.3": [ { "comment_text": "", "digests": { "md5": "fd288bced5bdc053cc0052017aa3ab32", "sha256": "92ef72e8b84b5caadf75779fd68dc5ff6ac36463ff32eeede6488926a01f86e0" }, "downloads": -1, "filename": "djextra-1.1.3.tar.gz", "has_sig": false, "md5_digest": "fd288bced5bdc053cc0052017aa3ab32", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11229, "upload_time": "2018-11-04T01:58:21", "url": "https://files.pythonhosted.org/packages/f2/4d/5cb7d1c2b50a4624276a2447cccaf73a20e6c1a0b621c81e8b7aae44d878/djextra-1.1.3.tar.gz" } ], "1.1.4": [ { "comment_text": "", "digests": { "md5": "a4ded8fb70e3f2fa29e03b1c623f37c0", "sha256": "707389ebd1eccdcf74bd681e1276036015d5793bf25c2b6911a58239d333798c" }, "downloads": -1, "filename": "djextra-1.1.4.tar.gz", "has_sig": false, "md5_digest": "a4ded8fb70e3f2fa29e03b1c623f37c0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11787, "upload_time": "2018-11-20T05:42:32", "url": "https://files.pythonhosted.org/packages/77/0c/bcc4ebac271a2efcd4d3b81a4eb09a6a93fec3557f49664f38f5c5c51f2a/djextra-1.1.4.tar.gz" } ], "1.1.6": [ { "comment_text": "", "digests": { "md5": "545cdec573b9b044fcf458c7a7761ab2", "sha256": "c0e8d4493dbebb0cc3b53f6178554a64c10cfa92492e67ebda4fae2f4aeb1c13" }, "downloads": -1, "filename": "djextra-1.1.6-py3-none-any.whl", "has_sig": false, "md5_digest": "545cdec573b9b044fcf458c7a7761ab2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 17932, "upload_time": "2019-01-14T03:11:28", "url": "https://files.pythonhosted.org/packages/95/49/9aa6c0e83591a9a44f48fc5cc92be00f0363c3026f29376c4438b4496a16/djextra-1.1.6-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3c721ad7a420e592178468695330b9f8", "sha256": "f46f0231dd26b52536ecbe149e0205700e4872dee38ab0e8167bfd89561b2e12" }, "downloads": -1, "filename": "djextra-1.1.6.tar.gz", "has_sig": false, "md5_digest": "3c721ad7a420e592178468695330b9f8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11903, "upload_time": "2019-01-14T03:11:29", "url": "https://files.pythonhosted.org/packages/60/a5/957523d1a9a5100e6828573f8957a3f4b4b5e8c10242dc4ea501af892565/djextra-1.1.6.tar.gz" } ], "1.1.7": [ { "comment_text": "", "digests": { "md5": "94c55558c615e80c4d4e2d4e41899693", "sha256": "94322cec0d307a29263572737d974ea8f86dd847b50940483a1c6a0a0509a5a2" }, "downloads": -1, "filename": "djextra-1.1.7.tar.gz", "has_sig": false, "md5_digest": "94c55558c615e80c4d4e2d4e41899693", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11900, "upload_time": "2019-01-14T03:17:57", "url": "https://files.pythonhosted.org/packages/e9/8b/27d3b33df7ef385e6aaa9ff4c0f835c10f2c43e8ec6f2ba2013f1bf4f1a0/djextra-1.1.7.tar.gz" } ], "1.1.8": [ { "comment_text": "", "digests": { "md5": "cb376ddfc6530206d01a29342488b37a", "sha256": "4251d690109bed3e712e2f66e4e89320a265b6ac9d653e6eb5b73c6e88854752" }, "downloads": -1, "filename": "djextra-1.1.8.tar.gz", "has_sig": false, "md5_digest": "cb376ddfc6530206d01a29342488b37a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11904, "upload_time": "2019-01-14T03:20:51", "url": "https://files.pythonhosted.org/packages/5f/c2/65104ea9a0baabba7d448965b87b2224642d0994943c4bbc0a6b9bcc8ae8/djextra-1.1.8.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "cb376ddfc6530206d01a29342488b37a", "sha256": "4251d690109bed3e712e2f66e4e89320a265b6ac9d653e6eb5b73c6e88854752" }, "downloads": -1, "filename": "djextra-1.1.8.tar.gz", "has_sig": false, "md5_digest": "cb376ddfc6530206d01a29342488b37a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11904, "upload_time": "2019-01-14T03:20:51", "url": "https://files.pythonhosted.org/packages/5f/c2/65104ea9a0baabba7d448965b87b2224642d0994943c4bbc0a6b9bcc8ae8/djextra-1.1.8.tar.gz" } ] }