Debugging and Profiling Open ERP
I've found the debug command by accident, but after reading all the other methods in the tools module, it turned out to be a really useful module. A short description of some debugging related commands follows.
- debug
- profile
- logged
The debug command
This one is even properly documented.
>>> from tools.misc import debug >>> pp debug.__doc__
Here follows its docstring:
This method allow you to debug your code without print Example:
>>> def func_foo(bar) ... baz = bar ... debug(baz) ... qnx = (baz, bar) ... debug(qnx) ... >>> func_foo(42)
This will output on the logger:
[Wed Dec 25 00:00:00 2008] DEBUG:func_foo:baz = 42 [Wed Dec 25 00:00:00 2008] DEBUG:func_foo:qnx = (42, 42)
To view the DEBUG lines in the logger you must start the server with the option --log-level=debug
The logged command
This one is even cooled than debug is! It prints data related to a function call. The arguments, the return value, and the time spent in the function. I've found it especially useful to find out the order of calls before a given method got called.
>>> from tools.misc import logged
The logged command is a decorator, so use it as such:
>>> @logged >>> def myfunc(arg): ... print arg
This one requires --log-level=debug too.
The profile command
The profile command uses Python's cProfiler to create a profile data file. This is a class decorator, and you should pass the output filename at init.
>>> from tools.misc import logged
>>> @profile('output.data')
>>> def myfunc(arg):
... print arg
KUDOS for these nice functions!
Hungarian banks in OpenERP / Hazai bankok az Open ERP-ban
Hungarian version at the bottom
We have just finished a new addon that contains all the Hungarian banks with BIC/SWIFT codes, and a nice search feature using the 3 number code every Hungarian bank account starts with. It is integrated with the bank form for partners.
You can grab it at lp:~toolpart/openobject-addons/res_bank_hu
Hungarian version follows
Elkészült a hazai bankokat tartalmazó Open ERP addonunk. Letölthető innen: lp:~toolpart/openobject-addons/res_bank_hu
Az addon tartalmazza az összes hazai bankok, BIC/SWIFT kóddal, és a bankszámla számnál megadott első 3 szám alapján keres is köztük, amikor bankot kívánunk partnerhez rendelni.
Our code
Summer is here, so we have plenty of free-time to do things we always wanted to do: sharing!
Even though we've regularly proposed our code changes for merging in openobject-server and openobject-web, finally we decided (whatta big decision! notta big decision, actually.) to put "our" mainline codes to launchpad as well. Thus even if a proposed merge gets rejected (or simply forgotten?) if someone likes what we do, they will find us.
- Our Open ERP server can be branched from lp:~toolpart/openobject-server/toolpart/
- Our Open ERP web client can be branched from lp:~toolpart/openobject-client-web/toolpart/
- Our Open ERP docs can be branched from lp:~toolpart/openobject-doc/toolpart/
I think you get the logic already. You are definitely encouraged to use our code!
Generating Open ERP docs: the beauty of sphinx
At ToolPart Team we always try to document as much of our code as possible. Documentation is present everywhere in our work:
- we write specifications to our clients
- we write comments in our code
- we write yaml test that work as documentation too
- the description field of our Open ERP addons serve the purpose of documentation
- etc
Unfortunately, our favourite ERP framework still lacks documentation. As we dive into it more and more, we always found undocumented parts. That last one was its workflow service. So, after understanding it, we documented it.
We decided to document the workflow service using docstring, and to use Sphinx' autodoc module to add the documentation into the Developers book.
Building the docs is described in the Open ERP Community book. But it wasn't that easy! We had already a system-wide sphinx installation, while openerp is installed in a virtualenv. Even if we installed sphinx under the virtualenv, it could not import the openerp modules. What goes wrong? We had no idea, but finally figured out.
It's not enough to have openerp-server, as a Pyton package under your sys.path's directory. We had to add the openerp-server directory directly using the PYTHONPATH environment variable. This is mentioned in the docs already, but it wasn't totally clear.
Thus running
PYTHONPATH=~/.virtualenvs/openerp6/lib/python2.6/site-packages/openerp-servermakehtmlNow builds the docs properly.
Elindult a Webstart szolgáltatás
A Toolpart Team Kft szolgáltatásai ismét kibővültek. Cégünk elindította legújabb szolgáltatását a Webstartot, amelynek segítségével percek alatt bárki létrehozhatja saját honlapját, mindenféle előképzettség nélkül. E program rendkívül egyszerű, mindenki számára könnyen kezelhető és bármikor igénybe vehető.
Önnek nincsen más dolga, mint kiválasztani a megfelelőt a folyamatosan frissülő és bővülő honlapvázakból, feltölteni a kívánt tartalmat (szövegen kívül még képet, térképet, videót is beilleszthet) és beállítani a tetszőleges menüket. Az ön honlapja így reklámok nélküli és teljesen keresőbarát honlap lesz.
Bővebb tájékoztatás a http://webstart.co.hu oldalon találhat, valamint megtekintheti mintaoldalunkat a http://mintasite.webstart.co.hu
Some useful text manipulation commands
In one of our works we have to import several csv files into Postgresql. Unfortunately, the files can't be imported without tweaking.
For example we have the following csv (but with dozens of columns):
Col1-bool;Col1-string;Col3-string;Col4-integer;Col5-float;... False;Hello;;NULL;5,6;...
Several things can be noted about this small csv snippet:
- As you can see, the csv file doesn't contain an id column. Actually, there might be no primary key columns at all.
- Second, the strings are unescaped. Thankfully, there are no line breaks!
- Third Col3 string column can't contain a NULL value
- Fourth, the NULL string doesn't mean a NULL value, but a NULL string, this it will cause an error
- Fifth, the float separator is , instead of.
So, how to correct all these problems easily? It's still a tedious task, but works out relatively easy with vim/sed and awk. Here comes a collection of the commands used.
:%s/^/\=strpart(line('.').";",0,5)
it's important to note the 0,5 part of strpart, this means to input the first 5 characters. This is enough for 9999 rows, but might be necessary to set to something higher otherwise.
How to deal with line breaks? Thankfully, they are often right at the beginning of a string. In these cases, this will do the trick:
sed '/;$/{N;s/\n//;}' file
strips a n off if line ends with a semicolon.
To get rid of not-NULL NULL strings we use awk:
awk -F';' 'BEGIN {OFS=";"} { if ($12=="") $12="###"; print }' infile.csv > outfile.csv
This means to treat the file as separated by semicolons (-F';'), and to change column 12 if it's empty to something strange, then print the whole line. Finally, we can use vim to change the strange characters to an empty string. As this was a rather common operation with varying column numbers, it would be nice to move it into a script where we can simply give all the column numbers to be checked, like getRidOfNull 12,24 infile.csv > outfile.csv.
Changing the NULL values is easy, just put in vim:
:%s/;NULL/;/g
Changing the floats:
:%s/\(\d\),\(\d\)/\1.\2/g
And finally, it's easy to convert binary csv files to pure ascii with enconv:
$ enca myfile.csv Universal character set 2 bytes; UCS-2; BMP
CRLF line terminators Byte order reversed in pairs (1,2 -> 2,1)
$ enconv myfile.csv
Tedious, but easy.
PyODBC telepítése Ubuntu alatt
Forrás: http://www.pauldeden.com/2008/12/how-to-setup-pyodbc-to-connect-to-mssql.html:
$ sudo aptitude install unixodbc unixodbc-dev freetds-dev tdsodbc python-dev $ pip install pyodbc
ennyi
Creating Screencasts with Ubuntu
From month to month, we are re-trying to create screencasts of our products, and user guides under Ubuntu, instead of Windows. Finally, we've managed to do it pretty easily.
Our screencast records a window session, and we prepend them with a simple static page where we introduce what the video will be about. To achieve this we use the following workflow:
Create the video
After several applications, we have settled with gtk-recordMyDesktop. We have tried XVidCap as well, but could not manage to have the sound recorded together with the video. With recordMyDesktop we have two annoyances. First, it does not have a proper area selection tool, only the small screenshot on its gui. Second, it creates ovg files, that later steps in our process can't use, so we need another action to convert its output to avi. Anyway, we are rather happy that it exists at least, and can be used smoothly.
Record the front page
To record the front page use gtk-recordMyDesktop again. First, create a simple slide in OpenOffice Presentations, and put it full-screen. Then start recordMyDesktop, and with this still image, record an introduction to your video.
Convert the files to avi
The simple command
mencoder-idxinput.ogv-ovclavc-oacmp3lame-ooutput.aviconverts our ogv file into avi.
Resize the front page
It's likely that you would like to capture only a portion of your screen for the screencast, but you capture the whole screen for the frontpage. To merge these two files you should first resize them to a common size.
We resize the fronpage. First, you should find out the output size. Open a video in Avidemux, and check out its properties under the File/Properties menu. Note down the sizes. To resize a video, open it in Avidemux, and change the Video and Audio modes to MPEG-4 ASP (XVid) and MP3 (lame) respectively. After this, you will be able to select filters from below the Video setting. Choose the MPlayer Resize filter, add it to the Active Filters list, and set the resize parameters as needed.
When you save the file, it will be resized as set up. Open you new file, and follow the merging as described below.
Merge the files
Open your first video in Avidemux, cut it as needed. Then Append your second video using the File/Append menu. Finally, save your work, and exit.
That's it. We only need these four steps to create a neat screenshot, that can be easily shared on the net.
Save your gtk-recordMyDesktop settings
gtk-recordMyDesktop creates a hidden file ~/.gtk-recordmydesktop to save your preferences. It's a good idea to save this file somewhere, for future use.
Fenntartható OpenERP telepítés
Ügyfeleink joggal kérik, hogy folyamatosan kövessük az általunk forgalmazott ERP rendszerek újabb és újabb változatait. Ezt mi karbantartási szerződésünk keretében természetesen vállaljuk is. Az alábbiakban bemutatjuk azt a néhány egyszerű technikát, amivel technológiai oldalról a rendszer frissítése biztonságosan végrehajtható.
Virtualenv
Először is fontos, hogy az OpenERP rendszereket külön-külön virtuális környezetbe telepítjük. Így - ha szükséges - könnyen vissza tudunk állni a korábbi kódra.
Adatbázis
Migráláskor különösen fontos az adatok megőrzése. Ezért fontos, hogy a létező adatokról biztonsági mentést készítünk, amit szükség esetén vissza tudunk tölteni a rendszerbe.
Előzetes tesztelés
Jól ismert axióma, hogy minden program tartalmaz hibákat. Ez azt is jelenti, hogy az új verziók akár új hibákat is tartalmazhatnak. Ennek elkerülése érdekében a rendszert a telepítés előtt saját cégünknél rakjuk fel, és ott használjuk. Így az esetleges hibákat még az ügyfélnek való telepítés előtt megtaláljuk. Külön figyelmet szentelünk a saját fejlesztésű modulok kompatibilitásának ellenőrzésére.
Biztonság az ügyfélnek
Megbízható partnerként, a maximumot hozzuk ki magunkból, és addig ülünk a gépeink előtt míg ügyfeleinknek megelégedetten nem bólintanak.
Pisa XHTML2PDF utf encoding problem
In our eKe project we wanted to generate pdf documents from html files. We have chosen XHTML2PDF for doing this.
We have used Pisa because we needed a simple and easily integrable pdf generator. For using pisa you need to include some modules from your python code and create your own templates for generating the document. Here is an example python code fragment:
def write_pdf(template_src, context_dict):
template = get_template(template_src)
context = Context(context_dict)
html = template.render(context)
result = StringIO.StringIO()
pdf = pisa.pisaDocument(StringIO.StringIO(
html.encode("UTF-8")), result)
if not pdf.err:
return http.HttpResponse(result.getvalue(), \
mimetype='application/pdf')
return http.HttpResponse('There was an error during pdf generation! %s' % cgi.escape(html))
So in theory it is very easy to use, we have manage to work it correctly for generating English texts in ten minutes. However we had a problem with Pisa when we wanted to create a PDF in Hungarian language. It was not generating the hungarian characters: ő ű Ő Ű (UTF-8 character encoding) instead it only showed rectangles. We have checked it with ISO-8859-2 encoding and it was working fine. This was not enough for us. We wanted to create a language independent program, where the users could choose from any language, from Turkish to Polish and Russian so we had to use UTF-8 encoding.
We have searched a lot of places on the Internet to solve this problem and according to the help of others this would work:
In the python code the pdf generating line is the following:
pdf = pisa.pisaDocument(StringIO.StringIO(
html.encode("UTF-8")), result, link_callback=fetch_resources,
encoding="utf-8", xhtml=True)
In the template file you have to insert this to the head section:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
And add this to your css file or html files "style" section.
@font-face {
font-family: "DejaVuSans";
src: url("http://www.toolpart.hu/static/media/medialibrary/2010/04/
DejaVuSans.ttf");
}
html {
font-family: "DejaVuSans";
}
But it still didn't worked. The problem was with the url of the font file. Somehow the program didn't recognized it. We have also tried it with
src: url( {{ MEDIA_URL }}fonts/DejaVuSans.ttf);
but it didn't worked this way.
Then we used absolute path (of django, not linux, so I think it should be no problem if you are using your application on different servers).
@font-face {
font-family: "DejaVu";
src: url(/static/fonts/DejaVuSans.ttf);
}
where /static/ is my {{ media_root }} directory.
This way you can use correct UTF-8 encodig in pdf documents generated with Pisa. Hope it helps.
Beszámoló a konferenciáról
Több mint 30 fordítóiroda képviselője gyűlt össze február 25-én, Budapesten, az ECE City Center, Corner Rendezvényközpontjában, hogyt részt vehessen a ToolPart Team Kft. által megszervezett „Fordítóirodák a középpontban” című fordítói szakmai konferencián. Amellett, hogy a rendezvényen innovatív jellegű programok kerültek bemutatásra, a résztvevők számára alkalom nyílt arra is, hogy mind egymással, mind pedig az előadókkal megtárgyalhassák a felmerülő kérdéseket, illetve, hogy egy nyílt fórum keretein belül tudjanak beszélgetni a szakmabeli hallgatóság többi tagjával az őket ért benyomásokról.

A Kilgray Kft. ügyvezető igazgatója, Kis Balázs részletes tájékoztatást nyújtott a MemoQ fordítástámogató szoftver alkalmazási lehetőségeiről az érdeklődőknek. A program konferencián említett tulajdonságai közt a legfontosabbak a moduláris felépítés, hálózati támogatottság, integráltság és a nagyfokú kompatibilitás voltak.
A Private Equity Tanácsadó Kft. képviselője, Havran Zsolt bővebb tájékoztatást nyújtott az ide vonatkozó magyarországi Európai Uniós pályázati lehetőségekről. Ezek a pályázatok információs és kommunikációs technológiai megoldások bevezetését célozzák meg, növelve ezáltal a kis- és középvállalkozások jövedelemtermelő és versenyképességét. Jelen esetet tekintve ezek a kezdeményezések a közeljövőben pozítiv hatással lesznek mind a fordítóirodák tevékenységére, mind pedig az Unió elképzeléseinek megvalósulására.

Our mostly loved and hated command
On a shared hosting @ webfaction having your permissions set up properly is definitely important and difficult. Especially, when you would like to give read access to your static web-accessible files.
Your three friends are
- getfacl,
- setfacl, and
- find
While the best ones are find . -type f -exec setfacl -m d:o:r-- '{}' ;
and it's so hard to find on the net!
Fordítóirodák a középpontban
Cégünk szakmai konferenciát szervez fordítók és fordítóirodák számára, melynek címe:
projektmenedzsment, fordító memória és pályázati támogatások
Előadó cégek:
- ToolPart Team Kft. (eKe)
- Kilgray Kft. (MemoQ)
- Private Equity Tanácsadó Kft.
A rendezvény helyszíne: ECE City Center irodaház, Corner Rendezvényközpont – Anker terem (1051 Budapest, Bajcsy-Zsilinszky út 12.)
A rendezvény időpontja: 2010. február 25., 8.00 óra
Program:
- 8.00 – 8.15
- regisztráció
- 8.15 – 9.00
- az eKe Translation Management Software bemutatása, (projektmenedzsment szoftver fordítóirodák számára). Előadó: ToolPart Team Kft.
- 9.00 – 9.30
- szünet
- 9.30 – 10.15
- a MemoQ fordító memória program ismertetése. Előadó: Kilgray Kft.
- 10.15 – 11.00
- fordítóirodák által igénybe vehető, aktuális pályázati lehetőségek ismertetése. Előadó: Private Equity Tanácsadó Kft.
Amennyiben Ön egyéni fordító vagy fordítóiroda tagja, és részt kíván venni a konferencián, részvételi szándékát, kérjük, az alábbi e-mail címünkön jelezze: toolpart@toolpart.hu
Bármilyen további kérdése van, szívesen állunk rendelkezésére elérhetőségeinken.
FeinCMS LocaleLookupMiddleware
As we are running a multilingual website we needed an easy way to place links around the web. Like we would like to put a link on a forum for translators so that they can find our eKe Translation Project Management software, but when they arrice on our site they get to the page with their request.LANGUAGE_CODE. This could be easily achieved with a simple middleware presented below.
from feincms.module.page.models import Page
from django.http import HttpResponseRedirect, Http404
from django.conf import settings
class LanguageLookupMiddleware(object):
'''
Redirect to a language specific page if it exists. First look up the request's
language, then the default language.
'''
def process_response(self, request, response):
if response.status_code != 404:
return response # No need to check for a flatpage for non-404 responses.
try:
language = [short for short, long in settings.LANGUAGES if short==request.LANGUAGE_CODE][0]
except KeyError:
pass
else:
response = self.get_page(language, request.path) and \
HttpResponseRedirect('/%s%s' % (language, request.path)) or response
if response.status_code == 404:
response = self.get_page(settings.LANGUAGES[0][0], request.path) and \
HttpResponseRedirect('/%s%s' % (settings.LANGUAGES[0][0], request.path)) or \
response
return response
def get_page(self, language, path):
try:
return Page.objects.page_for_path_or_404('/%s%s' % (language, path))
except Http404:
return False
What this middleware does? First it checks for a page request.LANGUAGE_CODE + request.path. If nothing is found then moves on and redirects you to settings.LANGUAGES[0][0] + request.path or gives a 404 error if the page does not exist.
How to translate your company?
As we work from a small country in a international company from the very beginning it was important for us to translate our website, every document we write on several languages, and create our software with proper i18n support. On this path we have already gathered some insights, and we hope you will find these useful too, and perhaps could share your view as well. So how do we hold our translations up-to-date on several languages?
First things first
First of all we have selected one of our collegues to be responsible for every translation job in the company. He is the one who coordinates the translators' work, receives new source and translated files as well, and sends them back to their source.
Second, as there are hundreds of languages in the world, we decided on a language preference list. No document is written before the same document exists on a more important language. (Was this correct english? Anyway, let's continue.)
- English
- German
- Russian
- Hungarian
- Roumanian
As we are from Hungary, and have people who speak good English and/or German, but never both, Hungarian actually become the 2nd on the list, but this was a must. So we always keep in mind that the German translations are more important to have them ready.
An interesting insight that came out from this ordering was that one of our collegues who was not very friendly with our international-openness actually accepted this decision, and his approach changed a lot thanks to this small decision. This showed us how important well articulated decisions are.
Bits and bytes
But what software do we use? Actually we use only one, but recommend two others. The reason is very simple. We use pootle to administer software translations. This is an immense help. We just wrote two scripts that syncs between our repository (managed with Bazaar) and pootle's, and that's all. This way our translation files are always up-to-date, and the translators can easily reach them whenever they see fit.
Then we have tested several tools to help the translators choose the best software. Our earlier work as IT consultant thought us that users are not always stupid, they are usually simply ignorant. Thus pointing them out some good applications can change their usual workflow. As we work with student and amateur translators to do the job we decided to check out these apps quickly as they often don't have the necessary background. Finally, we've found 5 notable applications:
- Trados
- MemoQ
- Virtaal
- OmegaT
- Google Translator Toolkit
The first two apps are closed source, proprietary apps for translators. They are well-known in the translating industry, but as we were looking for some software for students our main interest was in their feature lists. There we've learned terms like translation memory (TM), TM server, glossary, vocabulary etc... So we were looking for this functionality in the other applications.
We have found that virtaal is really nice and easy to use under Windows and Ubuntu 9.10+, but failed to make it run under Ubuntu 8.4 (LTS) in 5 minutes. Otherwise it seems the be a really nice software. Besides Virtaal, OmegaT can handle po files as well.
Then for our promotional material and website we have two tools, for the offline guys we recommend OmegaT. It's a really nice software, but a bit slow. On the other hand for the wired we opt for Google Translator Toolkit. in either case we use only one translation memory for everything (per language), and we expect that in the future this will allow us to get a huge efficiency gain.
As one tool is often better than two, we are going with OmegaT and have dropped Virtaal. But Google's Translator Toolkit stays still as it's the easiest to use.
Setting up OmegaT
An OmegaT project consists a whole directory structure. For every project you can define where your translation memories, glossary and dictionary files are stored, and you should put your translatable files under another subdirectory. Similarly the translated files will be generated in a 5th directory.
In order to gain the most synergy from our translations, we recommend to set up the system as the following directory structure:
translations/toolpart - tm - glossary - dictionary - project1 - target - project2 - target
while the source directory can be set to anywhere on the system.
This is our workflow. Do you have some other ideas? We would be really interested in them.
Related objects listing under the admin site
Sometimes you might not want to create admin inlines, but still want to link to some related data. We faced this problem in the eKe Project Management app where the related objects were often too complex to be shown as inlines. Thus we came up with a simple solution to extend the admin interface. Our solution simply gives a listing of related objects underneath the inline elements. It collects automatically all the reverse relationships ending on _set, but allows to exclude or register new relationships as well.

First of all we need a simple utility object. We had a brilliant idea for the name of this module: utils.py.
class AlreadyRegistered(Exception):
pass
class RelatedObjectLookup(object):
'''
Retrieves related objects defined by reverse relationships or given by the user.
'''
def __init__(self):
self._related = []
self._exclude = []
def register(self, relation):
'''
Add a relationship to be looked up
'''
if relation in self._exclude:
self._exclude.remove(relation)
if relation in self._related:
raise AlreadyRegistered("The relationship %s is alread registered" % relation)
self._related.append(relation)
def unregister(self, relation):
'''
Exclude a relationship from the lookup
'''
if relation in self._related:
self._related.remove(relation)
if relation in self._exclude:
pass
#raise AlreadyRegistered("The relationship %s is alread registered" % relation)
else:
self._exclude.append(relation)
def get_related_objects(self, klass):
'''
Get related objects as a dictionary of verbose_name_plural -> related_set.all()
'''
related = [field for field in dir(klass) if field.endswith('_set') and field not in self._exclude]
related.extend(self._related)
# get distinct values
related = {}.fromkeys(related).keys()
# get rid of wrong fields
for field in related:
try:
getattr(klass, field)
except AttributeError:
related.remove(field)
# sort by model name
def get_model_name_plural(field):
return getattr(klass, field).model._meta.verbose_name_plural
related = sorted(related, key=get_model_name_plural)
# put it all together
objects = []
for field in related:
try:
admin_add_url = getattr(klass, field).model.get_admin_add_url()
except AttributeError:
admin_add_url = False
objects.append({'model': getattr(klass, field).model._meta.verbose_name_plural,
'data': getattr(klass, field).all(),
'admin_add_url': admin_add_url,
})
return objects
def __call__(self, klass):
return self.get_related_objects(klass)
related_objects = RelatedObjectLookup()
This defines a simple registry class to add and retrieve related objects. To wire this up in our application we had to edit one of the admin templates. Thanks to django's template blocks finally we overwrite just the bare minimum. As we want to extend a change view, we should use its template $TEMPLATE_DIR/$YOUR_APP/$YOUR_MODEL/change_form.html is the template we should edit. Thankfully it contains a {{after_related_objects}} block that is perfect for our purpose.
{% extends "admin/change_form.html" %}
{% load i18n captureas %}
{% block after_related_objects %}
{% trans "Related objects" %}
{% for related_object in original.get_related_objects %}
{% captureas model_name_plural %}{{ related_object.model }}{% endcaptureas %}
{% blocktrans %}Related {{ model_name_plural }}{% endblocktrans %}
{% for element in related_object.data %}
{% if element.get_admin_change_url %}
- {{ element }}
{% else %}
- {{ element }}
{% endif %}
{% empty %}
- {% blocktrans %}No related {{ model_name_plural }} found{% endblocktrans %}
{% endfor %}
{% if related_object.admin_add_url %}
- {% trans "Add new" %}
{% endif %}
{% endfor %}
{% endblock %}
A couple of notes are in order here. First as we live in an international environment i18n is crucial for us. Unfortunately blocktrans can't handle non-simple variables, as a result we need the captureas template tags too. Once these are loaded we loop over {{ object.get_related_objects }}, but hey ... such a method does not exist yet! This will be the next step once we have finished explaining the template code.
In the loop we check for two more method calls {{ element.get_admin_change_url }} and {{ element.get_admin_add_url }}. As one might guess from the methods' name these provide the url to add and change a model instance. We opted for this approach because by default the RelatedObjectLookup class registers all the related objects, but by checking for these url methods we can easily filter out unnecessary objects; actually we just don't provide these methods.
But how do these methods look like and where do they live? Let's assume you have two models:
- Blog
- Post
Clearly we would like to show the related Posts for a Blog. To accomplish this we should add the following code to our two models
from utils import related_objects
class Blog(models.Model):
....
def get_related_objects(self):
return related_objects
class Post(models.Model):
....
@staticmethod
@models.permalink
def get_admin_add_url():
return ('admin:myapp_post_add',)
@permalink
def get_admin_change_url(self):
return ('admin:myapp_post_change', (self.pk,))
That's all! Of course, if we would like to add other objects we should extend utils.related_objects appropriately. We can do this either by importing it e.g. in your apps urls.py to extend the registered objects of a different app's model, or by simply extending it in the model's get_related_objects method. Actually, we are using both approaches in our project management application.
FeinCMS Integration with Mingus
After our site was finished we wanted to add a blog to it. Althought FeinCMS was the most suitable Content Management System for us, its blog content is more of an example and not really ussable. We have tried to make it work, but it was only a waste of time. You can see on http://groups.google.com/group/django-feincms that others have the same problem. We have searched the net to find a blog engine that was easy to use and we could easily integrate it with feincms. We have decided to use Mingus. Mingus itself leverages only existing reusable apps, and django-snippets. The heart of Mingus is the basic-blog from django-basic-apps. It is simple and well designed.
The integration of Mingus with FeinCMS was quite easy. I have downloaded the latest Django-Mingus package from GitHub. Then put it in the class path so my previously built FeinCMS page can access it, then installed all the apps from the requirements file.
settings.py: after this I have added the 'mingus' toINSTALLED_APPSI've had to merge mingus' settings with feincms'.urls.pyas feincms is set up to handle all the pages, it is the last entry in oururls.py. So I've merged mingus' urlpatterns by adding one lineurl(r'^blog/', include('mingus.urls')),
Template files: Mingus has a base.html that is extended by all their templates. We had a base.html as well, but it had nothing mingus related, and we didn't wanted to add everything to it as our feincms templates extend it as well. Moreover mingus uses much more javascript than our simple CMS pages, etc.. But Mingus has a blog/base_blog.html page as well. So we've decided to overwrite this one, and all the mingus extends to use this one. As sometimes we need disqus, this turned out to be a bit tricky. Finally, we've defined a new block called disqus that is usually rewritten to be empty.
- JavaScript and css: one of the reasons why we've chosen mingus was that it incorporates plenty of nice scripts, like the prettify javascript. Of course these should be added to our templates as well, moreover we had to copy/symlink them under
MEDIA_ROOT.
As a last step we've run syncdb, and now we have a nice mingus blog together with feincms.
Integrating WYMEditor with django
There are several possibilities to integrate a WYSIWYG editor under the admin. But what to do if you want a general solution that works with small differences under the admin and your main site? For our eKe Translation Project Management we opted to use WYMEditor, and wrote a couple of snippets to get it properly in the system.
The first step was to load WYMEditor for every textarea on a page. Using jQuery this was done by
$(document).ready(function() {
$("textarea").wymeditor({updateSelector: ":submit", updateEvent: "click"});
});
This works fine for a basic editor, but misses two things. First of all, as we build software for the global market, we should be able to specify the language WYMEditor uses. We can specify the current page's language in the html template, and pass that variable into $().wymeditor() as an option.
So we overwrote "admin/base_site.html" to add some javascript. The proper place seems to be the extrahead block, but actually this is false. As admin templates often include {{block.super}} only at the end of their extrahead overwrite, and the javascript above gets included in the extrahead (using the Media class of the given ModelAdmin) we have to add the script before extrahead. A possibility is the extrastyle block.
{% block extrastyle %}
{{ block.super }}
{% endblock %}
and add the necessary code to our wymeditor loader:
$(document).ready(function() {
var wym_options = {updateSelector: ":submit", updateEvent: "click"}
$.extend(wym_options, wym_custom);
$("textarea").wymeditor(wym_options);
});
Even better now, but what about the user frontend? Even the simplest contact us form is greatly enhanced by a well-working and simple WYSIWYG interface. Unfortunately, our current result is a bit confusing to the user as a v1.0 user has no clue what "Containers" and "Classes" might mean. So we should disable these. Similarly, one might prefer hiding the wymeditor logo as well. To achieve these we finally created a default.js file that is included in base.html and admin/base_site.html as well with the following code
varwym_defaults={updateSelector:":submit",updateEvent:"click",logoHtml:'',};and added to our base.html
<scriptlanguage="JavaScript"type="text/javascript">varwym_custom={lang:'{{ request.LANGUAGE_CODE|lower }}',containersHtml:'',classesHtml:'',}</script>and finally we initialize the wymeditors using
$(document).ready(function(){varwym_options={}$.extend(wym_options,wym_defaults,wym_custom);$("textarea").wymeditor(wym_options);});Now everything works like charm. Both our staff and users have a nice user experience, and we did not need much coding either. But we like coding! So to ease our future work we have created a simple Widget that adds the basic wymeditor script and our wymeditor loader script to the form's media
classGUITextAreaWidget(forms.Textarea):passclassMedia:js=("js/jquery.wymeditor.min.js","js/fancyedit.js")where fancyedit.js contains the wymeditor initializing code.
Use wysiwyg editor in django admin
When the site structure was finished we had to migrate our content to it. In FeinCMS there are two content types available for storing data: Raw Content and Rich Text. Raw Content is storing and showing simple html code. Rich Text also does the same, just it uses the TinyMCE Wysiwyg Editor to show end edit the data. TinyMCE is a simple javascript based wysiwyg editor what can be integrated to any textarea, because its javascript based, you have to enable javascript in you browser. This way in Rich Text it is much simpler to format texts, tags, links, etc. The latest Mingus also has the TinyMCE integrated to it so it can be used there, too.
In the following you can read the most simplest way to integrate TinyMCE with your admin page:
1. You can download TinyMCE from here.
2. Now, unzip the package you've just downloaded and go to "script" and copy the whole "tiny_mce" folder and put it in your settings.MEDIA_ROOT directory in your project.
3. Create a new HTML file at the following location: "templates/admin/base_site.html". This will replace the default "base_site.html" from the admin app templates.
4. Add the following code to the newly created file, reload your admin page and voila, it works:
{% extends "admin/base.html" %}
{% load i18n %}
{% block title %}{{ title }} | {% trans "Django Admin" %}{% endblock %}
{% block extrahead %}
<script type="text/javascript" src="/{{media_root}}/tiny_mce/tiny_mce.js"></script>
<script type="text/javascript">
tinyMCE.init({
// General options
mode : "textareas",
theme : "advanced",
plugins : "safari,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template",
// Theme options
theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,fontselect,fontsizeselect",
theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location : "bottom",
theme_advanced_resizing : true,
});
</script>
{% endblock %}FeinCMS
Our homepage is managed in FeinCMS, the best Content Management System in Django what is available on the Internet. FeinCMS has a few interesting features:
Page structuring: It lets you reorder page content blocks using a drag-drop interface, and you can add as many content blocks to a region (e.g. the sidebar, the main content region, etc.). It provides helper functions, which provide ordered lists of page content blocks.
Custom contents (ContactForm, RawContent, RichTextContent, ApplicationContent, etc.): FeinCMS knows nothing about content - just enough to create an admin interface for your own page content types. It allows you to easily add your own content types, which you can then manage through a javascript-enhanced interface.
Easily manageable multi-language support: You can connect together pages from different languages as translations of each other. During run-time in every page a link is available to its translations. This way a multi-language site can be easily created.