ckeditor not saving changes django - django

I have a form where in one of the fields, I use the ckeditor. However when I submit the form, the changes in the ckeditor field is not being saved. In the model, I have changed the field to aRichTextField. I have installed "ckeditor" in my apps in settings as well.
I have also both tried to load these scripts in my template:
{% load static %}
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
On top of that have I also tried to add the {{ form.media }} instead of the scripts but it does still not work.
I am using HTMX to dynamically update the form.
This is my form template right now
<form action='' method="POST" class="form" hx-post='' hx-swap='outerHTML'>
{% csrf_token %}
{{ form.media }}
<div class="form-group">
{% for field in form %}
{{ field }}
</div>
{% endfor %}
<br>
<div class='htmx-indicator'>Loading...</div>
<div class="text-center">
<button class='htmx-inverted-indicator' type='submit' >Save</button>
</div>
{% if message %}
<p>{{ message }}</p>
{% endif %}
</form>
Does anybody know why the form is not being saved?
EDIT
This is my view
#login_required
def book_update_view(request, id=None):
book = get_object_or_404(Book, id=id)
form = BookForm(request.POST or None, instance=book)
context = {
"form": form,
"object": book,
}
if form.is_valid():
form.save()
context['message'] = 'Saved!'
if request.htmx:
return render(request, "book/snippets/forms.html", context)
return render(request, "book/update.html", context)

Looks like there is a conflict between the CKEditor and HTMX. The below relies heavily on this answer. It makes the following changes:
Switches the HTMX labels to the button rather than the form
Applies an event
listener to the CKEditor - it does this via the {{field.label_tag}}
which is now included
Fixes up a misplaced tag
Try making your form something like this (don't forget to replace the name of the CKEditor field - you may need to check your source code to see how this is rendered):
<form method="post">
{% csrf_token %}
{{ form.media }}
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
var element = new CKEDITOR.dom.element( document.getElementById( '{{ form.NAMEOFCKEDITORFIELD.id_for_label }}' ) );
event.detail.parameters['{{ form.NAMEOFCKEDITORFIELD.html_name }}'] = element.getEditor().getData();
})
</script>
<div class="form-group">
{% for field in form %}
{{ field.label_tag }}:<br />{{ field }}
{% endfor %}
</div>
<br>
<div class='htmx-indicator'>Loading...</div>
<div class="text-center">
<button class='htmx-inverted-indicator' type='submit' hx-post="{% url 'book_update_view_name' book.id %}" hx-target="#{{form.id}}" hx-swap="outerHTML">Save</button>
</div>
{% if message %}
<p>{{ message }}</p>
{% endif %}

if you dont want extra get parameter which might give you a problem, you can put onclick on your submit button to have ckeditor transfer the data to your field. something like this :
<script>
function saveCK(){
let ckdata = CKEDITOR.instances.your_field_id.getData();
$('#your_field_id').val(ckdata);
}
</script>
yeah, sorry for the jquery, I'm not sure vanilla javascript equivalent.
and on your submit button, add
onclick="saveCK()"

One more option is to use hx-vals.
For example:
<button type='submit' hx-vals="js:{ {{ form.NAMEOFCKEDITORFIELD.name }}: CKEDITOR.instances['{{ form.NAMEOFCKEDITORFIELD.id_for_label }}'].getData()}" hx-post=POSTURL>Save</button>

Related

Why htmx trigger only work once in django

I am using htmx to trigger a field in Django ModelForm with the following codes.
Everything works as it supposed to the first time around, but after that when you change the option select field nothing happen, no trigger whatsoever. I have to reset and go back to url 'listing' for it to respond again. I want the code to trigger the result everytime I change the option select field before I finally submit. Any help is well appreciated.
class Listing(model.Model):
option=models.ForeignKey(Option,on_delete=models.CASCADE)
package=models.ForeignKey(Package,on_delete=models.CASCADE,blank=True,null=True)
number=models.ForeignKey(Number,on_delete=models.CASCADE,blank=True,null=True)
period=models.ForeignKey(Period,on_delete=models.CASCADE,blank=True,null=True)
title=models.CharField(max_length=20)
class ListingForm(ModelForm):
class Meta:
model=Listing
fields='__all__'
class ListingCreateView(CreateView):
model=Listing
form_class=ListingForm
template_name='listing_form.html'
success_url='/forms/listing/'
def option(request):
option=request.GET.get('option')
form=ListingForm
context={'option':option,'form':form}
return render(request,'partial_option.html',context)
urlpatterns=[
path('',ListingCreateView.as_view(),name='listing-create'),
path('option/',option,name='option'),
]
listing_form.html
{% load widget_tweaks %}
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org#1.6.1"></script>
</head>
<body>
<h1>Listing Form</h1>
<form method="post">
{% csrf_token %}
<div>
{{ form.option.label_tag }}
{% render_field form.option hx-get="/forms/option"
hx-trigger="change" hx-target="#option" hx-swap="outerHTML" %}
</div>
<div id="option"></div>
<input type="submit" value="Send">
</form>
<script>
document.body.addEventListener('htmx:configRequest', (event) =>
{
event.detail.headers['X-CSRFToken']='{{csrf_token}}';
})
</script>
</body>
</html>
partial_option.html:
{% if option %}
{% if option =='1' %}
<p>You have chosen option 1</p>
{% elif option == '2' %}
<p>You have chosen option 2</p>
{{ form.package.label_tag }}
{{ form.package }}
{% elif option == '3' %}
<p>You have chosen option 3</p>
{{ form.number.label_tag }}
{{ form.number }}
{{form.period.label_tag }}
{{ form.period }}
{% endif %}
{% else %}
<p>You have no option</p>
{% endif %}
{{ form.title.label_tag }}
{{ form.title }}
You have set the hx-swap="outerHTML" method, so HTMX will replace the target element with the response. Since your response does not contain a new <div id="option"> element, after the first request/swap cycle HTMX cannot find the target.
To solve this issue, change the swap method to innerHTML or embed the response in a <div id="option"></div> element.

Django Mixin to add context variables

I want to create a GenericFormMixin for Django that I can mix into CreateView and UpdateView that will let me use a generic form template by dynamically setting things like the page title, form title, submit button text, etc.
I have the following in mixins.py:
class GenericFormMixin(object):
page_title = ''
form_title = ''
submit_button_text = ''
Instead of having to create a modelname_create.html and modelname_update.html for every CreateView or UpdateView I have, I want to be able to use generic_form.html which will use the mixin's variables in its context to dynamically create an appropriately populated template:
{% extends "base.html" %}
{% block title %}{{ page_title }}{% endblock title %}
{% block content %}
<div class="panel panel-primary">
<div class="panel-heading">{{ form_title }}</div>
<div class="panel-body">
<form method="post" action=".">
{{ form }}
{% csrf_token %}
<button type="submit" class="btn btn-primary">{{ submit_button_text }}</button>
</form>
</div>
</div>
{% endblock content %}
My question is: now that I've created the mixin, how do I get the variables into the template context of CreateView and UpdateView?
Define get_context_data in your mixin. It should call the superclass method, add its elements to the dictionary returned from there, and then return the dict.

Why is this django formset not being submitted?

i have a formset as follows:
EduFormSet = formset_factory(forms.CandidateDegreeForm, can_delete=True)
edu_formset = EduFormSet(prefix='candidate_degree')
in the templates i am doing the following:
{% if edu_formset %}
{% for form in edu_formset %}
<div class="formset-form" style="visibility: visible;">
<form id="{{ form.prefix }}" method="POST" action="/degree/add/">
<h4>Some Heading Here</h4>
{% csrf_token %}
{% for field in form %}
{% include "form_field.html" %}
{% endfor %}
</form>
<script type="text/javascript">
jQuery(document).ready ( function(){
jQuery('{{ form.prefix }}').validationEngine();
});
</script>
<div class="clearfix"></div>
</div>
{% endfor %}
{{ edu_formset.management_form }}
<div class="button-container right">
<input class="button" type="submit" value="submit" />
</div>
{% endif %}
I am not sure why but nothing really happens when i hit the submit button.
Your submit button is not within the form, so the action is not triggered by the click!
Here's how the docs show you to render formsets:
<form method="post" action="">
<!-- Notice how the formset (below) and thus its submit button
is INSIDE the form (above) -->
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
You try to create multiple forms with the form.prefix for id. This could work but each form would have to be rendered with its own submit button. Formsets are designed to combine multiple forms into one and guarantee uniqueness of value names by said prefix. They would be enclosed in a singe form and share any submit triggers.

modal form embedded in html does not work in django

I'm making an application for food recipes and am trying to do the same html in the recipe and include comments in a modal window, the problem is that when I give I submit template fails and does not save the comment on the data base
urls.py
urlpatterns = patterns('recetas.apps.menus.views',
url(r'^recetas/$','recetas_view',name='vista_recetas'),
url(r'^reporte/$','reporte_receta',name='receta_reporte'),
url(r'^receta/(?P<id_receta>\d+)$','detalle_receta', name='vista_detalle'),
)
The html code that calls this url
<td><a href='/receta/{{ receta.id }}'>{{ receta.titulo }}</a></td>
views.py
def detalle_receta(request, id_receta):
dato = get_object_or_404(Receta, pk=id_receta)
comentarios = Comentario.objects.filter(receta=dato)
if request.POST:
if request.POST.get('cancel', id_receta):
return HttpResponseRedirect('/receta/{0}'.format(id_receta))
form = ComentarioForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/receta/{0}'.format(id_receta))
else:
form = ComentarioForm(initial={'receta': id_receta})
cxt = {'receta':dato,'comentarios':comentarios,'form':form}
return render_to_response('menus/receta.html', cxt, context_instance=RequestContext(request))
receta.html
{% extends 'base.html' %}
{% block titulo %}{{ receta.titulo }}{% endblock titulo %}
{% block estatico %}
<link rel='stylesheet' href='{{ STATIC_URL }}css/receta.css' type='text/css'>
<link rel='stylesheet' href='{{ STATIC_URL }}css/modal.css' type='text/css'>
<script type='text/javascript'>
function despliegaModal(valor) {
var elem = document.getElementById("bgVentanaModal");
elem.style.visibility = valor;
}
</script>
{% endblock estatico %}
{% block contenido %}
<div id="bgVentanaModal">
<div id="ventanaModal">
<form action="/receta/{{ receta.id_receta }}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Grabar">
<input name="cancel" type="submit" value="Cancelar">
</form>
</div>
</div>
<div id=receta>
<div id="nombre_receta">
<h1>{{receta.titulo|title}}</h1>
<hr>
</div>
<div id='ingredientes'>
<h2>Ingredientes</h2>
<p>{{ receta.ingredientes }}</p>
</div>
<div id='imagen'>
<img src='{{MEDIA_URL}}{{receta.imagen}}' width="480" height="300" >
</div>
<div id='preparacion'>
<h2>PreparaciĆ³n</h2>
<p>{{ receta.preparacion }}</p>
</div>
<div id='comentarios'>
<h2>Comentarios</h2>
{% for item in comentarios %}
<p>{{ item.texto}}</p>
{% empty %}
<p>Sin Comentarios registrados</p>
{% endfor %}
{% if user.is_authenticated %}
Agregue su comentario
{% endif %}
</div>
<div id="pie">
<hr>
<p>Receta Registrada el {{ receta.tiempo_registro|date:'SHORT_DATETIME_FORMAT' }} por {{ receta.usuario }}</p>
</div>
</div>
{% endblock contenido %}
everything works until I give the cancel key, does not validate the POST.
I believe the problem is in your view.py. Specifically in this part:
if request.POST.get('cancel', id_receta):
return HttpResponseRedirect('/receta/{0}'.format(id_receta))
That if will never result in a False value and, hence, your comment will never be saved. This has to do with how the dict.get function works:
get(key[, default])
Return the value for key if key is in the dictionary, else default. If default is not given, it defaults to None, so that this method never raises a KeyError.
So, if you click Grabar you'll get the default value (id_receta). Try the following instead:
if request.POST.get('cancel'): # Default to None
return HttpResponseRedirect('/receta/{0}'.format(id_receta))
A workaround would be just using a button with some JavaScript to redirect when you click Cancelar in your template:
views.py
def detalle_receta(request, id_receta):
dato = get_object_or_404(Receta, pk=id_receta)
comentarios = Comentario.objects.filter(receta=dato)
if request.POST:
# if request.POST.get('cancel', id_receta):
# return HttpResponseRedirect('/receta/{0}'.format(id_receta))
form = ComentarioForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/receta/{0}'.format(id_receta))
else:
form = ComentarioForm(initial={'receta': id_receta})
cxt = {'receta':dato,'comentarios':comentarios,'form':form}
return render_to_response('menus/receta.html', cxt, context_instance=RequestContext(request))
receta.html
...
...
<form action="/receta/{{ receta.id_receta }}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Grabar">
<input type="button" value="Cancelar" onclick="window.location.href='/receta/{{ dato.id }}'"/>>
</form>
...
...
Of course, you should use get_absolute_url instead of hardcoding URLs.
Thanks for help me Cesar, your answer help me, but my error is in the variable in receta.html
is {{ receta.id }} and in the button is the same the correct is
<form action="/receta/{{ receta.id }}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Grabar"/>
<input type="button" value="Cancelar" onclick="window.location.href='/receta/{{ receta.id }}'"/>
</form>
Thanks for your help and a friend who found the error

User settings in django admin

I want to be able to change some settings from django admin, for example: site title or footer. I want to have app with model which includes this settings, but this settings should be in single copy. What the best way to do it?
You can create a view with #staff_member_required decorator, which renders/saves a form:
from django.contrib.admin.views.decorators import staff_member_required
...
#staff_member_required
def edit_config(request, ):
saved = False
if request.method == "POST":
form = ConfigForm(request.POST)
if form.is_valid():
...
# Do saving here
saved = True
else:
form = ConfigForm()
...
context = {
'form': form,
'saved': saved,
}
return render_to_response('staff/edit_config.html', context, context_instance=RequestContext(request))
Use django forms in the view, and pass it to the template.
then, in the template extend 'admin/base_site.html' so your form has a admin look and feel. Here's a sample template:
{% extends 'admin/base_site.html' %}
{% load i18n adminmedia %}
{% block title %}Edit Configuration {{ block.super }} {% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
{% trans "Home" %} > Edit Configuration
</div>
{% endblock %}
{% block content %}
<h1>Edit Configuration</h1>
{% if saved %}
<p class="success" style="background-color:#9F9; padding: 10px; border: 1px dotted #999;">
Settings were saved successfully!
</p>
{% endif %}
<form method="POST" action="">
{% csrf_token %}
<fieldset class="module aligned">
<h2>Configuration</h2>
<div class="description"></div>
{% for field in form %}
<div class="form-row {% if field.errors %}errors{% endif %}">
{{ field.errors }}
<div class="field-box">
{{ field.label }} : {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</fieldset>
<div class="submit-row">
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save"/>
</div>
</form>
{% endblock %}
You can use database, ini files, redis, ... for storing your configuration. You may define some general backend, and inherit your custom backends from it so it's flexible.
Sounds like django-constance would be a good fit.
Though django-flatblocks might be sufficient.
django-flatblocks is a simple application for handling small
text-blocks on websites. Think about it like django.contrib.flatpages
just not for a whole page but for only parts of it, like an
information text describing what you can do on a site.