Commit 10fc46ee by Kálmán Viktor

dashboard: new vm create modal

parent 66c1cfd4
...@@ -23,48 +23,53 @@ VLANS = Vlan.objects.all() ...@@ -23,48 +23,53 @@ VLANS = Vlan.objects.all()
DISKS = Disk.objects.exclude(type="qcow2-snap") DISKS = Disk.objects.exclude(type="qcow2-snap")
class VmCreateForm(forms.Form): class VmCustomizeForm(forms.Form):
template = forms.ModelChoiceField(queryset=InstanceTemplate.objects.all(), name = forms.CharField()
empty_label="Select pls")
cpu_priority = forms.IntegerField() cpu_priority = forms.IntegerField()
cpu_count = forms.IntegerField() cpu_count = forms.IntegerField()
ram_size = forms.IntegerField() ram_size = forms.IntegerField()
disks = forms.ModelMultipleChoiceField( disks = forms.ModelMultipleChoiceField(
queryset=DISKS, queryset=None, required=True)
required=False
)
networks = forms.ModelMultipleChoiceField( networks = forms.ModelMultipleChoiceField(
queryset=VLANS, required=False) queryset=None, required=False)
template = forms.CharField()
customized = forms.CharField() # dummy flag field
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(VmCreateForm, self).__init__(*args, **kwargs) self.user = kwargs.pop("user", None)
self.template = kwargs.pop("template", None)
super(VmCustomizeForm, self).__init__(*args, **kwargs)
# set displayed disk and network list
self.fields['disks'].queryset = Disk.get_objects_with_level(
'user', self.user).exclude(type="qcow2-snap")
self.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', self.user)
# set initial for disk and network list
self.initial['disks'] = self.template.disks.all()
self.initial['networks'] = InterfaceTemplate.objects.filter(
template=self.template).values_list("vlan", flat=True)
# set initial for resources
self.initial['cpu_priority'] = self.template.priority
self.initial['cpu_count'] = self.template.num_cores
self.initial['ram_size'] = self.template.ram_size
# initial name and template pk
self.initial['name'] = self.template.name
self.initial['template'] = self.template.pk
self.initial['customized'] = self.template.pk
self.helper = FormHelper(self) self.helper = FormHelper(self)
self.helper.form_show_labels = False self.helper.form_show_labels = False
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Field("template", type="hidden"),
Div( Field("customized", type="hidden"),
Field('template', id="vm-create-template-select",
css_class="select form-control"),
css_class="col-sm-10",
),
css_class="row",
),
Div( # buttons Div( # buttons
Div( Div(
AnyTag(
"a",
HTML("%s " % _("Advanced")),
AnyTag(
"i",
css_class="vm-create-advanced-icon icon-caret-down"
),
css_class="btn btn-info vm-create-advanced-btn",
),
css_class="col-sm-5",
),
Div(
AnyTag( # tip: don't try to use Button class AnyTag( # tip: don't try to use Button class
"button", "button",
AnyTag( AnyTag(
...@@ -72,15 +77,21 @@ class VmCreateForm(forms.Form): ...@@ -72,15 +77,21 @@ class VmCreateForm(forms.Form):
css_class="icon-play" css_class="icon-play"
), ),
HTML(" Start"), HTML(" Start"),
css_id="vm-create-submit", css_id="vm-create-customized-start",
css_class="btn btn-success", css_class="btn btn-success",
), ),
css_class="col-sm-5 text-right", css_class="col-sm-11 text-right",
),
css_class="row",
),
Div(
Div(
Field("name"),
css_class="col-sm-5",
), ),
css_class="row", css_class="row",
), ),
Div( # vm-create-advanced
Div( Div(
Div( Div(
AnyTag( AnyTag(
...@@ -102,7 +113,8 @@ class VmCreateForm(forms.Form): ...@@ -102,7 +113,8 @@ class VmCreateForm(forms.Form):
Field('cpu_priority', id="vm-cpu-priority-slider", Field('cpu_priority', id="vm-cpu-priority-slider",
css_class="vm-slider", css_class="vm-slider",
data_slider_min="0", data_slider_max="100", data_slider_min="0", data_slider_max="100",
data_slider_step="1", data_slider_value="20", data_slider_step="1",
data_slider_value=self.template.priority,
data_slider_handle="square", data_slider_handle="square",
data_slider_tooltip="hide"), data_slider_tooltip="hide"),
css_class="col-sm-9" css_class="col-sm-9"
...@@ -120,7 +132,8 @@ class VmCreateForm(forms.Form): ...@@ -120,7 +132,8 @@ class VmCreateForm(forms.Form):
Field('cpu_count', id="vm-cpu-count-slider", Field('cpu_count', id="vm-cpu-count-slider",
css_class="vm-slider", css_class="vm-slider",
data_slider_min="1", data_slider_max="8", data_slider_min="1", data_slider_max="8",
data_slider_step="1", data_slider_value="2", data_slider_step="1",
data_slider_value=self.template.num_cores,
data_slider_handle="square", data_slider_handle="square",
data_slider_tooltip="hide"), data_slider_tooltip="hide"),
css_class="col-sm-9" css_class="col-sm-9"
...@@ -138,7 +151,8 @@ class VmCreateForm(forms.Form): ...@@ -138,7 +151,8 @@ class VmCreateForm(forms.Form):
Field('ram_size', id="vm-ram-size-slider", Field('ram_size', id="vm-ram-size-slider",
css_class="vm-slider", css_class="vm-slider",
data_slider_min="128", data_slider_max="4096", data_slider_min="128", data_slider_max="4096",
data_slider_step="128", data_slider_value="512", data_slider_step="128",
data_slider_value=self.template.ram_size,
data_slider_handle="square", data_slider_handle="square",
data_slider_tooltip="hide"), data_slider_tooltip="hide"),
css_class="col-sm-9" css_class="col-sm-9"
...@@ -257,8 +271,6 @@ class VmCreateForm(forms.Form): ...@@ -257,8 +271,6 @@ class VmCreateForm(forms.Form):
), ),
css_class="row" css_class="row"
), # end of network ), # end of network
css_class="vm-create-advanced"
),
) )
......
...@@ -2,24 +2,72 @@ var vlans = []; ...@@ -2,24 +2,72 @@ var vlans = [];
var disks = []; var disks = [];
$(function() { $(function() {
vmCreateLoaded(); vmCustomizeLoaded();
}); });
function vmCreateLoaded() { function vmCreateLoaded() {
$('.vm-create-advanced').hide(); $(".vm-create-template-details").hide();
$('.vm-create-advanced-btn').click(function() {
$('.vm-create-advanced').stop().slideToggle(); $(".vm-create-template-summary").click(function() {
if ($('.vm-create-advanced-icon').hasClass('icon-caret-down')) { $(this).next(".vm-create-template-details").slideToggle();
$('.vm-create-advanced-icon').removeClass('icon-caret-down').addClass('icon-caret-up'); });
} else {
$('.vm-create-advanced-icon').removeClass('icon-caret-up').addClass('icon-caret-down'); $(".customize-vm").click(function() {
var template = $(this).data("template-pk");
console.log(template);
$.get("/dashboard/vm/create/?template=" + template, function(data) {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
});
return false;
});
/* start vm button clicks */
$('.vm-create-start').click(function() {
template = $(this).data("template-pk");
$.ajax({
url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: {'template': template},
success: function(data, textStatus, xhr) {
if(data.redirect) {
window.location.replace(data.redirect + '#activity');
} }
else {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
$('body').append(data);
vmCreateLoaded();
addSliderMiscs();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
}); });
}
},
error: function(xhr, textStatus, error) {
var r = $('#create-modal'); r.next('div').remove(); r.remove();
$('#vm-create-template-select').change(function() { if (xhr.status == 500) {
vmCreateTemplateChange(this); addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
}); });
return false;
});
}
function vmCustomizeLoaded() {
/* network thingies */ /* network thingies */
/* add network */ /* add network */
...@@ -86,15 +134,24 @@ function vmCreateLoaded() { ...@@ -86,15 +134,24 @@ function vmCreateLoaded() {
/* copy networks from hidden select */ /* copy networks from hidden select */
$('#vm-create-network-add-vlan option').each(function() { $('#vm-create-network-add-vlan option').each(function() {
var managed = $(this).text().indexOf("mana") == 0; var managed = $(this).text().indexOf("mana") == 0;
var text = $(this).text(); var raw_text = $(this).text();
var pk = $(this).val(); var pk = $(this).val();
if(managed) { if(managed) {
text = text.replace("managed -", ""); text = raw_text.replace("managed -", "");
} else { } else {
text = text.replace("unmanaged -", ""); text = raw_text.replace("unmanaged -", "");
} }
var html = '<option data-managed="' + (managed ? 1 : 0) + '" value="' + pk + '">' + text + '</option>'; var html = '<option data-managed="' + (managed ? 1 : 0) + '" value="' + pk + '">' + text + '</option>';
if($('#vm-create-network-list span').length < 1) {
$("#vm-create-network-list").html("");
}
if($(this).is(":selected")) {
$("#vm-create-network-list").append(vmCreateNetworkLabel(pk, raw_text.replace("unmanaged -", "").replace("managed -", ""), managed));
} else {
$('#vm-create-network-add-select').append(html); $('#vm-create-network-add-select').append(html);
}
}); });
...@@ -168,8 +225,20 @@ function vmCreateLoaded() { ...@@ -168,8 +225,20 @@ function vmCreateLoaded() {
}); });
/* copy disks from hidden select */ /* copy disks from hidden select */
$('#vm-create-disk-add-select').html($('#vm-create-disk-add-form').html()); $('#vm-create-disk-add-form option').each(function() {
var text = $(this).text();
var pk = $(this).val();
var html = '<option value="' + pk + '">' + text + '</option>';
if($('#vm-create-disk-list span').length < 1) {
$("#vm-create-disk-list").html("");
}
if($(this).is(":selected")) {
$("#vm-create-disk-list").append(vmCreateDiskLabel(pk, text));
} else {
$('#vm-create-disk-add-select').append(html);
}
});
/* build up disk list */ /* build up disk list */
$('#vm-create-disk-add-select option').each(function() { $('#vm-create-disk-add-select option').each(function() {
...@@ -179,8 +248,8 @@ function vmCreateLoaded() { ...@@ -179,8 +248,8 @@ function vmCreateLoaded() {
}); });
}); });
/* add button */ /* start vm button clicks */
$('#vm-create-submit').click(function() { $('#vm-create-customized-start').click(function() {
$.ajax({ $.ajax({
url: '/dashboard/vm/create/', url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')}, headers: {"X-CSRFToken": getCookie('csrftoken')},
...@@ -219,97 +288,6 @@ function vmCreateLoaded() { ...@@ -219,97 +288,6 @@ function vmCreateLoaded() {
$('.js-hidden').hide(); $('.js-hidden').hide();
} }
function vmCreateTemplateChange(new_this) {
this.value = new_this.value;
if(this.value < 0) return;
$.ajax({
url: '/dashboard/template/' + this.value,
type: 'GET',
success: function(data, textStatus, xhr) {
if(xhr.status == 200) {
// set sliders
$('#vm-cpu-priority-slider').slider("setValue", data['priority']);
$('#vm-cpu-count-slider').slider("setValue", data['num_cores']);
$('#vm-ram-size-slider').slider("setValue", data['ram_size']);
/* slider doesn't have change event ........................ */
refreshSliders();
/* clear selections */
$("#vm-create-network-add-vlan").find('option').prop('selected', false);
$('#vm-create-disk-add-form').find('option').prop('selected', false);
/* clear the network select */
$("#vm-create-network-add-select").html('');
/* append vlans from InterfaceTemplates */
$('#vm-create-network-list').html("");
var added_vlans = []
for(var n = 0; n<data['network'].length; n++) {
nn = data['network'][n]
$('#vm-create-network-list').append(
vmCreateNetworkLabel(nn.vlan_pk, nn.vlan, nn.managed)
);
$('#vm-create-network-add-vlan option[value="' + nn.vlan_pk + '"]').prop('selected', true);
added_vlans.push(nn.vlan_pk);
}
/* remove already added vlans from dropdown or add new ones */
$('#vm-create-network-add-select').html('');
// this is working because the vlans array already has the icon's hex code
for(var i=0; i < vlans.length; i++)
if(added_vlans.indexOf(vlans[i].pk) == -1) {
var html = '<option data-managed="' + (vlans[i].managed ? 1 : 0) + '" value="' + vlans[i].pk + '">' + vlans[i].name + '</option>';
$('#vm-create-network-add-select').append(html);
}
/* enable the network add button if there are not added vlans */
if(added_vlans.length != vlans.length) {
$('#vm-create-network-add-button').attr('disabled', false);
} else {
$('#vm-create-network-add-select').html('<option value="-1">No more networks!</option>');
$('#vm-create-network-add-button').attr('disabled', true);
}
/* if there are no added vlans print it out */
if(added_vlans.length < 1) {
$('#vm-create-network-list').html("Not added to any network!");
}
/* append disks */
$('#vm-create-disk-list').html('');
var added_disks = []
for(var d = 0; d<data['disks'].length; d++) {
dd = data['disks'][d]
$('#vm-create-disk-list').append(
vmCreateDiskLabel(dd.pk, dd.name)
);
$('#vm-create-disk-add-form option[value="' + dd.pk + '"]').prop('selected', true);
added_disks.push(dd.pk);
}
/* remove already added disks from dropdown or add new ones */
$('#vm-create-disk-add-select').html('');
for(var i=0; i < disks.length; i++)
if(added_disks.indexOf(disks[i].pk) == -1)
$('#vm-create-disk-add-select').append($('<option>', {
value: disks[i].pk,
text: disks[i].name
}));
/* enable the disk add button if there are not added disks */
if(added_disks.length != disks.length) {
$('#vm-create-disk-add-button').attr('disabled', false);
} else {
$('#vm-create-disk-add-select').html('<option value="-1">We are out of &lt;options&gt; hehe</option>');
$('#vm-create-disk-add-button').attr('disabled', true);
}
}
}
});
}
function vmCreateNetworkLabel(pk, name, managed) { function vmCreateNetworkLabel(pk, name, managed) {
return '<span id="vlan-' + pk + '" class="label label-' + (managed ? 'primary' : 'default') + '"><i class="icon-' + (managed ? 'globe' : 'link') + '"></i> ' + name + ' <a href="#" class="hover-black vm-create-remove-network"><i class="icon-remove-sign"></i></a></span> '; return '<span id="vlan-' + pk + '" class="label label-' + (managed ? 'primary' : 'default') + '"><i class="icon-' + (managed ? 'globe' : 'link') + '"></i> ' + name + ' <a href="#" class="hover-black vm-create-remove-network"><i class="icon-remove-sign"></i></a></span> ';
......
{% load sizefieldtags %}
<div class="vm-create-template-list">
{% for t in templates %}
<div class="vm-create-template">
<div class="vm-create-template-summary">
{{ t.name }}
<span class="pull-right"><i class="icon-{{ t.os_type }}"></i> {{ t.system }}</span>
</div>
<div class="vm-create-template-details">
<ul>
<li>
<i class="icon-gears"></i> CPU
<div class="progress pull-right">
<div class="progress-bar progress-bar-success" role="progressbar"
aria-valuenow="{{ t.num_cores }}" aria-valuemin="0" aria-valuemax="8" style="width: 80%">
<span class="progress-bar-text">{{ t.num_cores }} cores</span>
</div>
</div>
</li>
<li>
<i class="icon-ticket"></i> Memory
<div class="progress pull-right">
<div class="progress-bar progress-bar-info" role="progressbar"
aria-valuenow="{{ t.ram_size }}" aria-valuemin="0" aria-valuemax="4096"
style="width: 80%">
<span class="progress-bar-text">{{ t.ram_size }} MB</span>
</div>
</div>
</li>
<li>
<i class="icon-file"></i> Disks
<span style="float: right;">
{% for d in t.disks.all %}{{ d.name }} ({{ d.size|filesize }}){% if not forloop.last %}, {% endif %}{% endfor %}
</span>
</li>
<li>
<i class="icon-globe"></i> Network:
<span style="float: right;">
{% for i in t.interface_set.all %}{{ i.vlan.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
</span>
</li>
<li>
<i class="icon-tag"></i> Típus: {{ t.lease.name }}
<span style="float: right;">
<i class="icon-pause"></i> {{ t.lease.get_readable_suspend_time }}
<i class="icon-remove"></i> {{ t.lease.get_readable_delete_time }}
</span>
</li>
<li>
<i class="icon-hand-right"></i> Description:
<span style="float: right; max-width: 350px;">
{{ t.description }}
</span>
<div class="clearfix"></div>
</li>
</ul>
<div style="margin-top: 20px; padding: 0 15px; width: 100%">
<a class="btn btn-primary btn-xs customize-vm" data-template-pk="{{ t.pk }}" href="{% url "dashboard.views.vm-create" %}?template={{ t.pk }}"><i class="icon-wrench"></i> Customize</a>
<form class="pull-right text-right" method="POST" action="{% url "dashboard.views.vm-create" %}">
{% csrf_token %}
<input type="hidden" name="template" value="{{ t.pk }}"/>
<button class="btn btn-success btn-xs vm-create-start" data-template-pk="{{ t.pk }}" type="submit"><i class="icon-play"></i> Start</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
<style>
.row {
margin-bottom: 15px;
}
.vm-create-template {
max-width: 800px;
border: 1px solid black;
border-bottom: none;
}
.vm-create-template-list .vm-create-template:last-child {
border-bottom: 1px solid black;
}
.vm-create-template-summary {
padding: 15px;
cursor: pointer;
}
.vm-create-template:nth-child(odd) .vm-create-template-summary {
background: #F5F5F5;
}
.vm-create-template-list .vm-create-template-summary:hover {
background: #D2D2D2;
}
.vm-create-template-details {
border-top: 1px dashed #D3D3D3;
padding: 15px;
}
.vm-create-template-details ul {
list-style: none;
padding: 0 15px;
}
.vm-create-template-details li {
border-bottom: 1px dotted #aaa;
padding: 5px 0px;
}
.progress {
position: relative;
width: 200px;
height: 12px;
margin-bottom: 0px;
margin-top: 5px;
}
.progress-bar-text {
position: absolute;
display: block;
width: 100%;
color: white;
/* outline */
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
font-size: 10px;
}
</style>
{% block "extra-js" %}
<script>
$('.progress-bar').each(function() {
var min = $(this).attr('aria-valuemin');
var max = $(this).attr('aria-valuemax');
var now = $(this).attr('aria-valuenow');
var siz = (now-min)*100/(max-min);
$(this).css('width', siz+'%');
});
</script>
{% endblock %}
{% load crispy_forms_tags %}
{% load sizefieldtags %}
{% crispy vm_create_form %}
<script src="/static/dashboard/vm-create.js"></script>
<div class="modal fade" id="create-modal" tabindex="-1" role="dialog"> <div class="modal fade" id="create-modal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
{% if box_title and ajax_title %}
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{{ box_title }}</h4> <h4 class="modal-title">{{ box_title }}</h4>
</div> </div>
{% endif %}
<div class="modal-body"> <div class="modal-body">
{% include template %} {% include template %}
</div> </div>
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
{% if template == "dashboard/vm-create.html" %} {% if template == "dashboard/_vm-create-1.html" %}
<script src="{{ STATIC_URL }}dashboard/vm-create.js"></script> <script src="{{ STATIC_URL }}dashboard/vm-create.js"></script>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% load crispy_forms_tags %}
<style>
.row {
margin-bottom: 15px;
}
</style>
<form method="POST" action="/dashboard/vm/create/">
{% csrf_token %}
{% crispy vm_create_form %}
</form>
...@@ -28,7 +28,8 @@ from django_tables2 import SingleTableView ...@@ -28,7 +28,8 @@ from django_tables2 import SingleTableView
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from .forms import ( from .forms import (
VmCreateForm, TemplateForm, LeaseForm, NodeForm, HostForm, DiskAddForm, VmCustomizeForm, TemplateForm, LeaseForm, NodeForm, HostForm,
DiskAddForm,
) )
from .tables import (VmListTable, NodeListTable, NodeVmListTable, from .tables import (VmListTable, NodeListTable, NodeVmListTable,
TemplateListTable, LeaseListTable, GroupListTable,) TemplateListTable, LeaseListTable, GroupListTable,)
...@@ -36,7 +37,6 @@ from vm.models import (Instance, InstanceTemplate, InterfaceTemplate, ...@@ -36,7 +37,6 @@ from vm.models import (Instance, InstanceTemplate, InterfaceTemplate,
InstanceActivity, Node, instance_activity, Lease, InstanceActivity, Node, instance_activity, Lease,
Interface) Interface)
from firewall.models import Vlan, Host, Rule from firewall.models import Vlan, Host, Rule
from storage.models import Disk
from dashboard.models import Favourite from dashboard.models import Favourite
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -952,7 +952,7 @@ class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): ...@@ -952,7 +952,7 @@ class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
class VmCreate(LoginRequiredMixin, TemplateView): class VmCreate(LoginRequiredMixin, TemplateView):
form_class = VmCreateForm form_class = VmCustomizeForm
form = None form = None
def get_template_names(self): def get_template_names(self):
...@@ -962,51 +962,66 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -962,51 +962,66 @@ class VmCreate(LoginRequiredMixin, TemplateView):
return ['dashboard/nojs-wrapper.html'] return ['dashboard/nojs-wrapper.html']
def get(self, request, form=None, *args, **kwargs): def get(self, request, form=None, *args, **kwargs):
if form is None: form_error = form is not None
form = self.form_class() template = (form.template.pk if form_error
form.fields['disks'].queryset = Disk.get_objects_with_level( else request.GET.get("template"))
'user', request.user).exclude(type="qcow2-snap")
form.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', request.user)
templates = InstanceTemplate.get_objects_with_level('user', templates = InstanceTemplate.get_objects_with_level('user',
request.user) request.user)
form.fields['template'].queryset = templates if form is None and template:
form = self.form_class(user=request.user,
template=templates.get(pk=template))
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
if template:
context.update({ context.update({
'template': 'dashboard/vm-create.html', 'template': 'dashboard/_vm-create-2.html',
'box_title': 'Create a VM', 'box_title': _('Customize VM'),
'ajax_title': False,
'vm_create_form': form, 'vm_create_form': form,
'template_o': templates.get(pk=template),
})
else:
context.update({
'template': 'dashboard/_vm-create-1.html',
'box_title': _('Create a VM'),
'ajax_title': False,
'templates': templates.all(),
}) })
return self.render_to_response(context) return self.render_to_response(context)
def post(self, request, *args, **kwargs):