{ "info": { "author": "Kenneth Love", "author_email": "kennethlove@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: User Interfaces" ], "description": "# django-shapeshifter\n\nA common problem in Django is how to have a view, especially a class-based view\nthat can display and process multiple forms at once. `django-shapeshifter` aims\nto make this problem much more trivial.\n\nRight now, `django-shapeshifter` can handle any (well, theoretically) number of\nforms in a single view. A view class is provided for multiple standard forms\nor model forms. To mix and match these form types, you'll need to do a little\nextra work. Here's how to use the package:\n\n## Installation\n\n`$ pip install django-shapeshifter`\n\nYou should not need to add `shapeshifter` to your `INSTALLED_APPS`.\n\n## Usage\n\nYou use `django-shapeshifter` just like you use Django's built-in class-based\nviews. You should be able to use the provided views with most mixins you're\nalready using in your project, such as `LoginRequiredMixin`. Certain mixins may have to be refactored, such as `SuccessMessageMixin`, which is trigged on the `form_valid()` method. \n\nLet's look at using the view with a few standard forms:\n\n*interests/views.py*\n```python\nfrom django.urls import reverse_lazy\n\nfrom shapeshifter.views import MultiFormView\n\nfrom . import forms\n\n\nclass InterestFormsView(MultiFormView):\n form_classes = (forms.ContactForm, forms.InterestsForm, forms.GDPRForm)\n template_name = 'interests/forms.html'\n success_url = reverse_lazy('interests:thanks')\n```\n\nBut what do you need to do in the template? The view's context will contain\na new member, `forms`, that you can iterate over to display each form:\n\n*interests/templates/interests/forms.html*\n```html\n{% extends 'layout.html' %}\n\n{% block content %}\n

Please fill out your interests below!

\n\n
\n{% csrf_token %}\n{% for form in forms %}\n {{ form.as_p }}\n{% endfor %}\n \n
\n{% endblock content %}\n```\n\nThis will generate a template with all three forms, in succession, inside of a\nsingle `
` tag. **All of the forms must be submitted together.** After\nsubmission, Django will fill each form in with the appropriate submitted data,\nvalidate them, and then redirect to your `success_url`.\n\nBut with just the above code, nothing will happen with the form data. To control\nthat, you need to override the `forms_valid` method in your view. Here's what\nthat might look like:\n\n*interests/views.py*\n```python\nclass InterestsFormView(MultiFormView):\n ...\n def forms_valid(self):\n forms = self.get_forms()\n contact_form = forms['contactform']\n interest_form = forms['interestsform']\n gdpr = forms['gdprform']\n\n if not gdpr.data['accept']:\n messages.error(\"You must accept the GDPR terms.\")\n return HttpResponseRedirect(reverse_lazy('interests:forms'))\n salesforce_client.send(zip(contact_form.data.items(),\n interest_form.data.items()))\n return super().forms_valid()\n```\n\nThe above code isn't meant to be a complete example but should give you an idea\nof what would be done to handle the form data.\n\n### What about model forms?\n\nAll of the above code is valid for model forms, too, with one exception. For\nmodel forms, instead of extending `MultiFormView`, you'll extend\n`MultiModelFormView`. There are two major differences between the classes but\nthe most important one is that `forms_valid` will call `form.save()` on each\nform. Here is an example allowing a user to edit their `User` `first_name` and `last_name`, and their first `Profile` `name` on one form:\n\n*my_app/models.py*\n```python\nfrom django.contrib.auth.models import User\n\nclass Profile(models.Model):\n name = models.CharField(max_length=255)\n user = models.ForeignKey(User, related_name='profiles', on_delete=models.CASCADE)\n```\n\n*my_app/forms.py*\n```python\nfrom django.contrib.auth.models import User\n\nfrom .models import Profile\n\nclass UserForm(forms.ModelForm):\n class Meta:\n model = User\n\n fields = [\n 'first_name',\n 'last_name',\n ]\n\nclass ProfileForm(forms.ModelForm):\n class Meta:\n model = Group\n fields = [\n 'name',\n ]\n\n labels = {\n 'name': 'Profile Name',\n }\n```\n\n*my_app/views.py*\n```python\nfrom shapeshifter.views import MultiModelFormView\nfrom shapeshifter.mixins import MultiSuccessMessageMixin\n\nfrom .forms import UserForm, ProfileForm\n\nclass UserUpdateView(LoginRequiredMixin, MultiSuccessMessageMixin, MultiModelFormView):\n form_classes = (UserForm, ProfileForm)\n template_name = 'my_app/forms.html'\n success_url = reverse_lazy('home')\n\n def get_instances(self):\n instances = {\n 'userform': self.request.user,\n 'profileform': profile_instance = Profile.objects.filter(\n user=self.request.user,\n ).first(),\n }\n\n return instances\n```\n\n### What if I want to mix model and standard form?\n\nThat's fine! You will have to override `forms_valid` in your view to handle the\nprocessing of each form but everything else should work exactly the same.\n\n## API\n\n`MultiFormView` (and `MultiModelFormView` by inheritance) extends Django's\n`TemplateView`. Additionally it adds a few methods for the instantiation and\nprocessing of the forms. Any and all of these can be overwritten to customize\nthe behavior of your views.\n\nBelow is each attribute and their default value, and each method with its\nsignature and return value.\n\n### Attributes\n\n* `initial = {}` - Initial values for each form. Should be a `dict` formatted\nwith the following format:\n\n```python\ninitial = {\n 'contactform': {\n 'name': 'Katherine Johnson'\n }\n}\n```\n\nwhere `ContactForm` is the class name of the form you're providing initial\nvalues for.\n\n* `form_classes = None` - a list or tuple of `Form` (or `ModelForm` if using\n`MultiModelFormView`) classes. **Do not instantiate the class, just provide the\nname**).\n\n* `success_url = None` - the URL to redirect users to once the forms are all filled in\ncorrectly. This can be a URL or a `reverse_lazy` instance.\n\n### Methods\n\n* `get_form_classes(self)` - Returns the view's `form_classes` attribute.\nOverride this method if you need to dynamically set the forms that should be\nincluded in the view.\n\n* `get_forms(self) -> dict` - Instantiates each form, using the `kwargs` from\n`get_form_kwargs` and returns them all as a dict with the key being a standardized\nversion of the form's class name. Override this if you need to change how the\nforms are instantiated.\n\n* `get_form_class_name(self, form_class) -> str` - Converts the form's class\nname into a lowercase string. `ContactForm` will become `contactform`. You can\noverride this to provide for a different standardized name for your forms.\n\n* `get_form_kwargs(self, form_class) -> dict` - Returns a dict of keyword\narguments for the form's creation. Prefixes each form with the lowercased class\nname, provides any `initial` arguments for the form, and, if the view was\nrequested as either `POST` or `PUT`, provides both `data` and `files` to the\nform. For `MultiModelFormView`, this method also provides the `instance` for the\nform. Override this method to add or change the form kwargs.\n\n* `validate_forms(self) -> bool` - Calls `form.is_valid()` for each form and\nreturns the result for the entire set of forms. Override this method if your\nforms require any special validation steps.\n\n* `forms_valid(self)` - This method is called if all forms pass validation. In\n`MultiFormView`, this method simply redirects to the `success_url`. For the \nmodel-based version, `MultiModelFormView`, this method calls `form.save()` on\neach form and then redirects. Override this method to change what happens\nwhen the forms are all valid.\n\n* `forms_invalid(self)` - If any of the forms fail their validation check, this\nmethod is executed. By default, it re-renders the view, presenting the forms\nwith their errors. You can override this method if you need something else to\nhappen when not all forms are valid.\n\n### `MultiModelFormView`'s extra attributes and methods\n\nAs mentioned above, a few things are handled differently in `MultiModelFormView`.\n\n* `instances = {}` - This attribute should be a dict with lowercase form class\nnames as keys. The values should be the instance to use for the form.\n\n* `get_instances(self)` - Returns the value of `instances`. Override this if\nyou need to dynamically fetch the instances for the forms.\n\n### `MultiSuccessMessageMixin` attributes and methods\n\n* `success_message = None` - A string containing a success message to add through Django's messages framework.\n\n* `get_success_message(self)` - A method which returns the success message. Defaults to self.success_message.\n\n* `forms_valid(self)` - Returns the response after adding the success message.\n\n## Contributing\n\nThank you for your interest, time, and energy! Contributions are always\nwelcome and will be reviewed as quickly as possible (that said, we're all\nvolunteers with other jobs/responsibilities so it might be awhile).\n\nPlease fork this repository and make your changes in the `shapeshifter` package.\nBe sure to add a test for any functionality changes. Once all tests pass, you\ncan submit a pull request with your changes, the rationale behind them, and\nany special steps the maintainers will need to take to test your changes or\nreplicate the bug you're fixing. **Be sure to include adding your name to the\nfollowing list of contributors!**\n\n### Contributors\n\n* Kenneth Love\n* Lacey Williams Henschel\n* Tim Allen\n\n## What's with the name? And the version?\n\nThe original name was already taken so a new one had to be found. Since this\npackage deals with multiple forms, `shapeshifter` was a good pun (shapeshifters\ncan take on many forms).\n\nThe version number is based on the date of release.\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/kennethlove/django-shapeshifter", "keywords": "forms,django,multi,cbv,class-based views", "license": "Apache 2.0", "maintainer": "", "maintainer_email": "", "name": "django-shapeshifter", "package_url": "https://pypi.org/project/django-shapeshifter/", "platform": "", "project_url": "https://pypi.org/project/django-shapeshifter/", "project_urls": { "Homepage": "https://github.com/kennethlove/django-shapeshifter" }, "release_url": "https://pypi.org/project/django-shapeshifter/18.9.23/", "requires_dist": null, "requires_python": "", "summary": "Class-based views for simultaneously handling multiple forms in Django", "version": "18.9.23" }, "last_serial": 4302973, "releases": { "18.8.27": [ { "comment_text": "", "digests": { "md5": "f7f8c26e1d3753167e0347a466342590", "sha256": "99eda403185fc501b71fdb1f11da5c43cf07764c5bd6dfe1464ac6a9597a544f" }, "downloads": -1, "filename": "django_shapeshifter-18.8.27-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "f7f8c26e1d3753167e0347a466342590", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 13950, "upload_time": "2018-08-27T16:43:03", "url": "https://files.pythonhosted.org/packages/49/44/16ff667f6dcf0c7d5af83d47ea6731a323fdc5b3c74dece1d0c88961a1af/django_shapeshifter-18.8.27-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5cb40354952f5c5e86641fdfb8f0c1b0", "sha256": "f3f6005d4acb2c8974ec1c32c19cc7c2c50b8d58e61f1e66f40cc66675cd8ba1" }, "downloads": -1, "filename": "django-shapeshifter-18.8.27.tar.gz", "has_sig": false, "md5_digest": "5cb40354952f5c5e86641fdfb8f0c1b0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12167, "upload_time": "2018-08-27T16:43:05", "url": "https://files.pythonhosted.org/packages/57/d3/70bad278c87b4ec6dc0eb5362ef268d9c284e5138b243004be307de524f6/django-shapeshifter-18.8.27.tar.gz" } ], "18.9.23": [ { "comment_text": "", "digests": { "md5": "eaee3a43ffd4543fc2cd6ed89628ecdf", "sha256": "e9a829587610a40a7d03fb19436413bc92450c1bf00e90927836a8717b130bc7" }, "downloads": -1, "filename": "django_shapeshifter-18.9.23-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "eaee3a43ffd4543fc2cd6ed89628ecdf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 15587, "upload_time": "2018-09-23T20:32:07", "url": "https://files.pythonhosted.org/packages/89/76/7ae0b65b7a44dceb4d67a80a95aa5743c74dd19d1713e4dbae0b14b62090/django_shapeshifter-18.9.23-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c3448ab3947c6e0c00954fe3a5ca41f2", "sha256": "7e127f00f155eb5c01acb2bcaeb46f7919451d4956b1b14ecde0a485d9f8339d" }, "downloads": -1, "filename": "django-shapeshifter-18.9.23.tar.gz", "has_sig": false, "md5_digest": "c3448ab3947c6e0c00954fe3a5ca41f2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13508, "upload_time": "2018-09-23T20:32:09", "url": "https://files.pythonhosted.org/packages/d2/3e/7c4255b76ad7f7528fa4d563aa577c75fb7b07658259e853a606eea2c4c8/django-shapeshifter-18.9.23.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "eaee3a43ffd4543fc2cd6ed89628ecdf", "sha256": "e9a829587610a40a7d03fb19436413bc92450c1bf00e90927836a8717b130bc7" }, "downloads": -1, "filename": "django_shapeshifter-18.9.23-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "eaee3a43ffd4543fc2cd6ed89628ecdf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 15587, "upload_time": "2018-09-23T20:32:07", "url": "https://files.pythonhosted.org/packages/89/76/7ae0b65b7a44dceb4d67a80a95aa5743c74dd19d1713e4dbae0b14b62090/django_shapeshifter-18.9.23-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c3448ab3947c6e0c00954fe3a5ca41f2", "sha256": "7e127f00f155eb5c01acb2bcaeb46f7919451d4956b1b14ecde0a485d9f8339d" }, "downloads": -1, "filename": "django-shapeshifter-18.9.23.tar.gz", "has_sig": false, "md5_digest": "c3448ab3947c6e0c00954fe3a5ca41f2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13508, "upload_time": "2018-09-23T20:32:09", "url": "https://files.pythonhosted.org/packages/d2/3e/7c4255b76ad7f7528fa4d563aa577c75fb7b07658259e853a606eea2c4c8/django-shapeshifter-18.9.23.tar.gz" } ] }