I have a model with an HTMLField which can be edited with a TinyMCE control in the admin. However, I would like to be able to give different options to TinyMCE depending on which instance of the model is being edited. How can I do this?
(For example, if the user is editing the SimplePage instance whose slug is technologies, I want TinyMCE to use the default CSS file, but if its editing the SimplePage whose slug is ticker, I want to use a different CSS file.)
I guess you have a Media class in your ModelAdmin with additional JavaScript and CSS for the admin (like here). Your JavaScript doesn't know the slug of the current object, let's change that.
First create one of the following directory structures in your templates directory: "admin/your-app" for an app or "admin/your-app/your-model" for a specific model only (see the Django documentation).
Then create a file "change_form.html" in that directory and put something similar to this in there:
{% extends "admin/change_form.html" %}
{% block extrahead %}
<script type="text/javascript" charset="utf-8">
var MYAPP_objectSlug = "{{ original.slug|escapejs }}";
</script>
{{ block.super }}
{% endblock %}
This will extend the usual "change_form.html" of the admin and extend the extrahead block to set a JavaScript variable with your object slug (original is your object).
Now adapt the JavaScript file that does the tinyMCE.init to use a different CSS file based
on the JavaScript variable MYAPP_objectSlug.
if (MYAPP_objectSlug == "ticker"){
var MYAPP_cssFile = "../css/special.css"; // change to your path
} else {
var MYAPP_cssFile = "../css/default.css"; // change to your path
}
tinyMCE.init({
...
content_css : MYAPP_cssFile,
...
});
Related
Was wondering if it is possible to add a custom button within a Wagtail model page that will allow me to save (create) the current data fields and move on to another page. The "save and add another" button was already available in django and I want to have something like that on the Wagtail model page.
Thanks.
Wagtail has a few ways to customise content that is shown in the various Wagtail modeladmin views.
Code Example
Step 1 - create a custom CreateView
This will override the get_context_data method to provide the create_url, we could do this simpler (the context already has access to the instance) but it is nice to be explicit.
Override the get_success_url method to allow for a URL param of next to be set, if that exists the next URL will be this instead of the default behaviour.
products/wagtail_hooks.py
from wagtail.contrib.modeladmin.views import CreateView
# ... other imports
class CustomCreateView(CreateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['create_url'] = self.create_url
return context
def get_success_url(self):
next = self.request.GET.get('next')
if next:
return next
return super().get_success_url()
class ProductsModelAdmin(ModelAdmin):
create_view_class = CustomCreateView
Step 2 - Override the template to add a new button
Wagtail modeladmin uses a template path override approach, you will need to create a new template that aligns with your desired override (for example we may just want to override the product model create view only.
templates/modeladmin/products/products/create.html -> this says to override the create template for the products model within the products app.
Add the code below and check it is working, sometimes the templates path can be a bit tricky so ensure you can see the new button AND that the button has the data-next attribute that should be your create URL.
templates/modeladmin/products/products/create.html
{% extends "modeladmin/create.html" %}
{% load i18n wagtailadmin_tags %}
{% block form_actions %}
{{ block.super }}
<div class="dropdown dropup dropdown-button match-width">
<button type="submit" class="button button-secondary action-save button-longrunning" data-next="{{ create_url }}" data-clicked-text="{% trans 'Saving…' %}">
{% icon name="spinner" %}<em>{% trans 'Save & add another' %}</em>
</button>
</div>
{% endblock %}
Step 3 - Add JS to modify the form action URL on click
The next step requires a bit of JavaScript, we want to attach a different behaviour to the 'save and add another' button.
The JS used here does not require jQuery and should work in IE11
Once the DOM is loaded (which means JS can do things), find all the buttons with the data-next attribute.
Add a listener to the 'click' of each of those buttons which will dynamically update the form action attribute with the extra URL part.
This extra URL part gets read by the get_success_url method in the custom create class but only on a successful save, otherwise you will stay on the page and be able to fix errors.
{% block extra_js %}
{{ block.super}}
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('[data-next]').forEach(function(button) {
button.addEventListener('click', function(event) {
var form = document.querySelector('.content-wrapper form');
form.action = form.action + '?next=' + button.dataset.next;
});
});
})
</script>
{% endblock %}
I've been trying to override the functioning of +(add) button for related fields in Django admin to open a new tab instead of a popup. I looked at the RelatedObjectLookup.js to see how it works but still stuck at implementing the same functioning by opening a new tab. Is there any way to implement such a thing or to render the form 'inline'?
To open related fields +Add button in a new tab, you have to set target="_blank" attribute for all those links.
Override admin/change_form.html from your admin.
class BookAdmin(admin.ModelAdmin):
add_form_template = 'book/admin/change_form.html'
In the html, set the required attribute and remove
{% extends 'admin/change_form.html' %}
{% load static %}
{% block admin_change_form_document_ready %}
{{ block.super }}
<script type="text/javascript">
(function($) {
$(document).ready(function() {
classes = document.getElementsByClassName('add-related');
for (i=0; i<classes.length; i++) {
// set target to blank
classes[i].setAttribute('target', '_blank');
// remove the class to prevent django listeners overriding click on link
classes[i].classList.remove("related-widget-wrapper-link");
};
});
})(django.jQuery);
</script>
{% endblock %}
Now when you click on related fields, it will open in a new tab.
An alternate option is to use inline admin as mentioned here in docs.
I'd like to make a simple website running on the GAE framework/service. It's very small, and i don't really need the whole powerful Django framework, and thus i'm opting for the google-made Webapp2 framework, coupled with the Jinja2 templating langage.
I'm coming from a really bare-PHP-and-HTML-oriented background, so I have a hard time adjusting to the way a real framework works. My current greatest interrogation comes from how the templating system and the request handlers are working together, especially if the the page's template has several "dynamic" elements.
I'll first explain what I used to do in PHP, so you may better understand what i want to achieve.
Let's say I want a website with :
a page title depending on the page being visited, eg : "Mysite.com | Control Panel"
a dynamic menu bar, that may change depending on the user's profile or logged-in status
obviously, a page body that completely depends on the page being viewed
The way i'd do it in PHP is thus here compressed into a simple example, index.php:
<?php
/*here use the $_GET or $_POST variable, and the $_SESSION variable
to figure out who's connected, which page is being displayed,
and store those values in global variables, for the
included modules to use */
include('page_header.php'); // renders the whole <head> </head> tag and its content
echo "<body>";
include('views/menu.php'); //generates the menu, displays it
switch($page_name){
case "home":
include('home.php'); //renders the page body for the homepage
break;
case "articles":
include('home.php'); //renders the page body for the blog articles listing
break;
case "guestbook":
include('home.php'); //renders the page body for the guestbook
break;
}
echo "</body>";
Each included module, using variables from the script that called them (index.php), and the $_POST, $_GET, $_SESSIOn superglobals, figures out what to display, and renders it to HMTL. here index.php also does some kind of very basic routing, using the switch statement.
Now,back to webapp2 and jinja2 framework:
I understand that, to have a modular approach to build a web page with Jinja, you need to use block structures, and extend those blocks. Thus, to build a similar page to the previous PHP example, i made the following template base.html:
<html>
<head>
<link rel="stylesheet" href="/static/css/reset.css">
{% block title %}
{% endblock title %}
</head>
<body>
<div class="menu">
{% block menu %}
{% endblock menu %}
</div>
<div class="body">
{% block content %}
{% endblock content %}
</div>
</body>
</html>
What i don't understand, is how you you'll build the different Handlers that, in turn, generate the context that Jinja will use to render the blocks, and avoiding redundency.
**Also, can I use several different template files that, alone, extend only one block (eg: menu.html, header.html, body_home.html, body_articles.html, ...) **
You can use as a base to answer, this example, from a small example that almost taught me all i needed to know.
Thanks for any help provided, sorry for any grammatical errors, english's not my native tongue.
There's a feature in jinja2 called macros which is pretty much a parameterized include.
So if the includes should be parametized, you would do:
{% macro menu(params) -%}
do something
{%- endmacro %}
And call the macro in your template:
<div> menu('bar') </dib>
If it is not necesary to provide parameters just leave it as static html in the parent template.
For the handler you can follow App Engine hello world example, just use your link to guide you to load jinja's enviroment.
I know you want to translate the php example, but try to do as much logic as posible in your handler instead of the template.
To your second question, yes you can extend just one block if you want, and if you need the parents content there's a {{super()}} block to get them.
{{ form.media.js }} and {{ form.media.css }} are great to easily include media in Django templates.
My problem is that I would like to get access to the raw urls (without <\script type="text/javascript" src="raw_url"><\/script>) of these media (to include them using headjs).
I'd like to achieve something like that:
<script>
{% for script in form.media.js %}
head.js("{{ script.raw_url }}");
{% endfor %}
</script>
If you take a look at source code of Media class there will be a function:
def render_js(self):
return [u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path) for path in self._js]
So you can go the same way. Since we can't use underscored attributes in templates we can add method to the form class like this and use it:
from django import forms
class TestForm(forms.Form):
def get_media_js(self):
return [self.media.absolute_path(path) for path in self.media._js]
Can anyone describe how I implement the ckeditor in django.contrib.flatpages?
Few steps to get this done. First, make sure ckeditor.js is being served up in some way from django. Info on this can be found at http://docs.djangoproject.com/en/1.2/howto/static-files/#howto-static-files. For this example, I will be serving it from 127.0.0.1:8000/js/ckeditor/ckeditor.js.
You'll need to override the standard flatpage change form template. In your templates directory, create a file in the following subdirectory: <your templates dir>/admin/flatpages/flatpage/change_form.html
Create the following text inside:
{% extends "admin/change_form.html" %}
{% block extrahead %}
{{ block.super }}
<script type="text/javascript" src="/js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" charset="utf-8">
var $ = jQuery = django.jQuery.noConflict(); // Use djangos jquery as our jQuery
</script>
<script type="text/javascript" src="/js/ckeditor/adapters/jquery.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready( function(){
$( 'textarea' ).ckeditor({
"skin":"kama",
"width" : 850,
// "toolbar" : "Basic", // uncomment this line to use a basic toolbar
});
});
</script>
{# Adding some custom style to perty thing up a bit. #}
<style type="text/css">
div>.cke_skin_kama{
width: 100%;
padding: 0!important;
clear: both;
}
</style>
{% endblock %}
The first few lines contain django's default text for the extrahead block. The rest of the script imports the ckeditor javascripts and uses django's already-imported jQuery with the ckeditor jQuery adapter. Finally, we end up forcing some style upon the page, as by default, things look a bit messy.
From here, you can quickly change the toolbar by implementing different options in the ckeditor call. Going to a simple toolbar is likely something you'll need if non-technical people are going to edit these flatpages. You can just uncomment that line in the above code to implement that.
Found good solution:
This is a little tricky. I've got admin.autodiscover() in my urls.py,
so it's automatically going to create an admin for flatpages as
defined in django.contrib.flatpages. I certainly don't want to go
hacking apart something that came with Django, nor do I want to give
up the convenience of autodiscover.
http://www.elidickinson.com/story/django-flatpages-and-ckeditor/2011-11
For a solution without template hacking check this page:
http://johansdevblog.blogspot.it/2009/10/adding-ckeditor-to-django-admin.html
I report here the example if the link goes down.
This is a simple model.
from django.db import models
class SampleModel(models.Model):
title = models.CharField(max_length=50)
text = models.TextField()
def __unicode__(self):
return self.title
This is how to add ckeditor support to a specific type of field, in this case a TextArea.
from sampleapp.models import SampleModel
from django.contrib import admin
from django import forms
from django.db import models
class SampleModelAdmin(admin.ModelAdmin):
formfield_overrides = { models.TextField: {'widget': forms.Textarea(attrs={'class':'ckeditor'})}, }
class Media:
js = ('ckeditor/ckeditor.js',) # The , at the end of this list IS important.
admin.site.register(SampleModel,SampleModelAdmin)
At this point the ckeditor.js will check for all the textarea with the class attribute set to "ckeditor".