Skip to content

Instantly share code, notes, and snippets.

@melvyn-sopacua
Created March 5, 2017 22:50
Show Gist options
  • Save melvyn-sopacua/2e848fa5b8be0f53c2a5267ba68d570e to your computer and use it in GitHub Desktop.
Save melvyn-sopacua/2e848fa5b8be0f53c2a5267ba68d570e to your computer and use it in GitHub Desktop.
Display update form in a modal with initial data
from django.views import generic
from django.db.models.base import Model
from collections import OrderedDict
from django.forms import BaseForm
from django.core.validators import RegexValidator
__all__ = (
'AdditionalFormsMixin',
)
class AdditionalFormsManager(OrderedDict):
# Allow only these chars to be able to call them as variables in the
# template.
_name_validator = RegexValidator(
regex=r'^\w+$', message='Form name contains unsupported characters'
)
def register_form(self, name: str, form_class: BaseForm, action_url: str,
is_multipart: bool=False,
model_instance: Model=None) -> None:
"""
Register a form with the manager
The name is validated to be useful as template variable and a
ValiditionError is raised if this is not the case.
:param name: form name, used as variable in the context
:param form_class: class of the form to add to the context
:param action_url: url the form should be submitted to
:param is_multipart: if the form should be capable of uploading files
:param model_instance: if the form is a model form used for update,
it will update this instance.
"""
self._name_validator(name)
formdata = {
'form_class': form_class,
'action_url': action_url,
'is_multipart': is_multipart,
'model_instance': model_instance,
}
self[name] = formdata
def get_additional_form(self, name: str) -> tuple:
try:
formdata = self.get(name)
except KeyError:
raise KeyError('No form with name {} found.'.format(name))
form_class = formdata.pop('form_class')
instance = formdata.pop('model_instance')
form = form_class(instance=instance) if instance else form_class()
return form, formdata
def get_context_update(self) -> dict:
update = {}
for name in self.keys():
form, data = self.get_additional_form(name)
update[name] = form
for prop in data:
propname = '{}__{}'.format(name, prop)
update[propname] = data[prop]
return update
class AdditionalFormsMixin(generic.base.ContextMixin):
def __init__(self, *args, **kwargs):
self._afm = AdditionalFormsManager()
super().__init__(*args, **kwargs)
def add_additional_form(self, name: str, form_class: BaseForm,
action_url: str, **kwargs) -> None:
"""
Convenience proxy to `AdditionalFormsManager.register_form()`.
Also forwards-compatible with any additional keyword arguments it may
choose to support.
:param name: form name, used as variable in the context
:param form_class: class of the form to add to the context
:param action_url: url the form should be submitted to
"""
self._afm.register_form(name, form_class, action_url, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self._afm.get_context_update())
return context
{% extends "base.html" %}
{% load l10n bootstrap3 %}
{% block form_media %}{{ project_update_form.media }}{% endblock %}
{% block content %}
{# deleted for brevity #}
<nav class="col-md-2 col-md-offset-1" role="navigation">
<ul class="nav nav-stacked nav-pills">
<li role="presentation">
<a href="#project-update" data-toggle="modal">Edit</a>
</li>
</ul>
</nav>
<div class="modal fade" id="project-update">
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal" action="{{ project_update_form__action_url }}" method="post">
{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Edit project</h4>
</div>
<div class="modal-body">
{% bootstrap_form project_update_form layout="horizontal" %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
{% endblock %}
from django.views import generic
from django.utils.translation import ugettext_lazy as _
from .. import models, forms
from .mixins import AdditionalFormsMixin
from django.urls import reverse
class ProjectDetailView(AdditionalFormsMixin, generic.DetailView):
model = models.Project
context_object_name = 'project'
template_name = 'view/project.html'
def get(self, request, *args, **kwargs):
instance = self.get_object()
url = reverse('project_edit', kwargs={'slug': instance.slug})
self.add_additional_form(
'project_update_form', forms.ProjectForm, url,
model_instance=instance
)
return super().get(request, *args, **kwargs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment