Merge branch 'qbe' into test
authorEric Mc Sween <eric.mcsween@auf.org>
Tue, 5 Jun 2012 14:01:20 +0000 (10:01 -0400)
committerEric Mc Sween <eric.mcsween@auf.org>
Tue, 5 Jun 2012 14:01:20 +0000 (10:01 -0400)
Conflicts:
versions.cfg

28 files changed:
buildout.cfg
project/menu.py
project/settings.py
project/urls.py
src/qbe/.gitignore
src/qbe/CHANGES.txt
src/qbe/MANIFEST.in
src/qbe/django_qbe.egg-info/dependency_links.txt [new file with mode: 0644]
src/qbe/django_qbe.egg-info/not-zip-safe [new file with mode: 0644]
src/qbe/django_qbe.egg-info/top_level.txt [new file with mode: 0644]
src/qbe/django_qbe/admin.py [new file with mode: 0644]
src/qbe/django_qbe/exports.py
src/qbe/django_qbe/forms.py
src/qbe/django_qbe/migrations/0001_initial.py [new file with mode: 0644]
src/qbe/django_qbe/migrations/0002_auto__add_field_savedquery_user_updated__add_field_savedquery_user_cre.py [new file with mode: 0644]
src/qbe/django_qbe/migrations/__init__.py [new file with mode: 0644]
src/qbe/django_qbe/models.py [new file with mode: 0644]
src/qbe/django_qbe/static/django_qbe/css/qbe.css
src/qbe/django_qbe/static/django_qbe/img/cut.png [changed mode: 0644->0755]
src/qbe/django_qbe/static/django_qbe/js/qbe.core.js
src/qbe/django_qbe/static/django_qbe/js/qbe.diagram.js
src/qbe/django_qbe/templates/admin/django_qbe/savedquery/change_form.html [new file with mode: 0644]
src/qbe/django_qbe/templates/qbe.html
src/qbe/django_qbe/templates/qbe.js
src/qbe/django_qbe/templates/qbe_results.html
src/qbe/django_qbe/utils.py
src/qbe/django_qbe/views.py
versions.cfg

index aec15b1..89e204e 100644 (file)
@@ -43,6 +43,7 @@ eggs =
     auf.django.workflow
     auf.recipe.django
     odfpy
+    django-picklefield
     pygraphviz
     simplejson
 
index be01a27..48db575 100644 (file)
@@ -64,6 +64,12 @@ class CustomMenu(Menu):
                         items.MenuItem('Organigramme par bureau', reverse('admin:rh_regionproxy_changelist')),
                         ]
                     ),
+                items.MenuItem('Requêtes',
+                    children=[
+                        items.MenuItem('Requêtes sauvegardées', reverse('admin:django_qbe_savedquery_changelist')),
+                        items.MenuItem('Constructeur de requêtes', reverse('qbe_form')),
+                        ]
+                    ),
             ]
         super(CustomMenu, self).init_with_context(context)
         
index b1f94fa..4084a7e 100644 (file)
@@ -175,3 +175,22 @@ DATABASE_ROUTERS = (
 
 # django-sendfile
 SENDFILE_BACKEND = 'sendfile.backends.simple'
+
+QBE_DISPLAY_DATABASES = False
+
+# Il est *très* important de respecter la case.
+QBE_CUSTOM_MODELS = {'Rh': {
+    'Employe': {},
+    'Poste': {},
+    'Dossier': {},
+    'Remuneration': {},
+    'Contrat': {},
+}}
+
+QBE_ALLOWED_FIELDS = {'Rh': {
+    'Employe': ['nom', 'prenom', 'genre', 'date_naissance', 'situation_familiale', 'date_entree'],
+    'Poste': ['nom', 'date_debut', 'date_fin'],
+    'Dossier': ['statut_residence', 'regime_travail', 'regime_travail_nb_heure_semaine', 'date_debut', 'date_fin'],
+    'Remuneration': ['montant', 'date_debut', 'date_fin'],
+    'Contrat': ['date_debut', 'date_fin'],
+}}
index 3680123..2239575 100644 (file)
@@ -32,4 +32,6 @@ urlpatterns = patterns(
     url(r'^dae/', include('project.dae.urls'), decorators=['django.contrib.auth.decorators.login_required']),
     url(r'^recrutement/', include('recrutement.urls'), decorators=['django.contrib.auth.decorators.login_required']),
     url(r'^rh/', include('project.rh.urls'), decorators=['django.contrib.auth.decorators.login_required']),
+
+    url(r'^qbe/', include('django_qbe.urls')),
 )
index d323f3b..69430df 100644 (file)
@@ -1,6 +1,6 @@
 *.xml
 *.pyc
-*.egg-info
+.*.swp
 /artworks
 /backups
 /base
@@ -8,5 +8,6 @@
 /django_extensions
 /media
 /olwidget
+/django_qbe.egg-info
 /admin.py
 /*.sh
index 57bdbae..073d799 100644 (file)
@@ -1,3 +1,8 @@
+0.1.X
+-----
+- Fix joining with models which have db_table set in _meta
+- Fix get labels translated
+
 0.1.6 (2011-04-20)
 ------------------
  - Added compatibility with Django 1.3 static files.
index 9dfc4b5..ba01c18 100644 (file)
@@ -5,4 +5,4 @@ include LICENSE-AGPL.txt
 include MANIFEST.in
 include README.rst
 recursive-include django_qbe/templates *
-recursive-include django_qbe/media *
+recursive-include django_qbe/static *
diff --git a/src/qbe/django_qbe.egg-info/dependency_links.txt b/src/qbe/django_qbe.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/qbe/django_qbe.egg-info/not-zip-safe b/src/qbe/django_qbe.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/qbe/django_qbe.egg-info/top_level.txt b/src/qbe/django_qbe.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..59ecab7
--- /dev/null
@@ -0,0 +1 @@
+django_qbe
diff --git a/src/qbe/django_qbe/admin.py b/src/qbe/django_qbe/admin.py
new file mode 100644 (file)
index 0000000..77dc37e
--- /dev/null
@@ -0,0 +1,73 @@
+from hashlib import md5
+from django.conf import settings
+from django.http import HttpResponseRedirect
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+from django.contrib import admin
+from django.contrib.admin.util import unquote
+from django_qbe.utils import pickle_encode
+from django_qbe.models import SavedQuery
+
+class SavedQueryAdmin(admin.ModelAdmin):
+    list_display = ('name', 'description', 'date_created', 'run_link')
+    exclude = ('user_created', 'user_updated')
+
+    def run_link(self, obj):
+        info = self.model._meta.app_label, self.model._meta.module_name
+        pickled = pickle_encode(obj.query_data)
+        query_hash = md5(pickled + settings.SECRET_KEY).hexdigest()
+        return u'<span class="nowrap"><a href="%s">%s</a> | <a href="%s?hash=%s">%s</a></span>' % \
+            (reverse("admin:%s_%s_run" % info, args=(obj.pk,)), _("Run"),
+             reverse("qbe_form"), query_hash, _("Edit & Run"))
+    run_link.short_description = _("query")
+    run_link.allow_tags = True
+
+    def get_urls(self):
+        from django.conf.urls.defaults import patterns, url
+        from django.utils.functional import update_wrapper
+
+        def wrap(view):
+            def wrapper(*args, **kwargs):
+                return self.admin_site.admin_view(view)(*args, **kwargs)
+            return update_wrapper(wrapper, view)
+
+        info = self.model._meta.app_label, self.model._meta.module_name
+        urlpatterns = patterns('',
+            url(r'^(.+)/run/$',
+                wrap(self.run_view),
+                name='%s_%s_run' % info,
+                kwargs={'extra_context': {
+                    'test': 'test',
+                }}),
+        )
+        return urlpatterns + super(SavedQueryAdmin, self).get_urls()
+
+    def save_model(self, request, obj, form, change):
+        query_hash = request.GET.get("query_hash", "")
+        query_key = "qbe_query_%s" % query_hash
+        obj.query_data = request.session[query_key]
+
+        if not change:
+            obj.user_created = request.user
+
+        obj.user_updated = request.user
+        obj.save()
+
+    def add_view(self, request, form_url='', extra_context=None):
+        query_hash = request.GET.get("query_hash", "")
+        query_key = "qbe_query_%s" % query_hash
+        if not query_key in request.session:
+            return HttpResponseRedirect(reverse("qbe_form"))
+        return super(SavedQueryAdmin, self).add_view(request, form_url=form_url, extra_context=extra_context)
+
+    def run_view(self, request, object_id, extra_context=None):
+        obj = self.get_object(request, unquote(object_id))
+        data = obj.query_data
+        pickled = pickle_encode(data)
+        query_hash = md5(pickled + settings.SECRET_KEY).hexdigest()
+        query_key = "qbe_query_%s" % query_hash
+        if not query_key in request.session:
+            request.session[query_key] = data
+        return HttpResponseRedirect(reverse("qbe_results", args=(query_hash, )))
+
+admin.site.register(SavedQuery, SavedQueryAdmin)
index 55cbb0c..5f5e8e5 100644 (file)
@@ -2,6 +2,10 @@
 import codecs
 import csv
 from StringIO import StringIO
+from odf.opendocument import OpenDocumentSpreadsheet
+from odf.style import Style, TextProperties, TableColumnProperties
+from odf.text import P
+from odf.table import Table, TableRow, TableCell
 
 from django.http import HttpResponse
 from django.utils.datastructures import SortedDict
@@ -61,9 +65,16 @@ class UnicodeWriter(object):
             self.writerow(row)
 
 
-def base_export(labels, results):
+def txt(msg):
+    text = msg
+    if msg is not None and not isinstance(msg, unicode):
+        text = unicode (msg, "utf-8")
+    return text
+
+
+def base_export(labels, results, dialect=csv.excel_tab):
     output = StringIO()
-    w = UnicodeWriter(output)
+    w = UnicodeWriter(output, dialect=dialect)
     w.writerow(labels)
     for row in results:
         w.writerow(row)
@@ -73,20 +84,56 @@ def base_export(labels, results):
 
 @formats.add("csv")
 def csv_format(labels, results):
-    output = base_export(labels, results)
+    output = base_export(labels, results, dialect=csv.excel)
     mimetype = "text/csv"
-    return HttpResponse(output, mimetype=mimetype)
+    response = HttpResponse(output, mimetype=mimetype)
+    response['Content-Disposition'] = 'attachment; filename=export.csv'
+    return response
 
 
 @formats.add("ods")
 def ods_format(labels, results):
-    output = base_export(labels, results)
+    doc = OpenDocumentSpreadsheet()
+    style = Style(name="Large number", family="table-cell")
+    style.addElement(TextProperties(fontfamily="Arial", fontsize="15pt"))
+    doc.styles.addElement(style)
+    widewidth = Style(name="co1", family="table-column")
+    widewidth.addElement(TableColumnProperties(columnwidth="2.8cm", breakbefore="auto"))
+    doc.automaticstyles.addElement(widewidth)
+
+    table = Table()
+    if len (labels) > 0:
+        tr = TableRow ()
+        table.addElement (tr)
+        for item in labels:
+            tc = TableCell ()
+            tr.addElement (tc)
+            p = P(stylename = style, text = txt(item))
+            tc.addElement (p)
+
+    for line in results:
+        tr = TableRow ()
+        table.addElement (tr)
+        for item in line:
+            tc = TableCell ()
+            tr.addElement (tc)
+            p = P (stylename = style, text = txt(item))
+            tc.addElement (p)
+
+    doc.spreadsheet.addElement(table)
+    buffer = StringIO ()
+    doc.write(buffer)
+
     mimetype = "application/vnd.oasis.opendocument.spreadsheet"
-    return HttpResponse(output, mimetype=mimetype)
+    response = HttpResponse(buffer.getvalue(), mimetype=mimetype)
+    response['Content-Disposition'] = 'attachment; filename=export.ods'
+    return response
 
 
 @formats.add("xls")
 def xls_format(labels, results):
     output = base_export(labels, results)
     mimetype = "application/vnd.ms-excel"
-    return HttpResponse(output, mimetype=mimetype)
+    response = HttpResponse(output, mimetype=mimetype)
+    response['Content-Disposition'] = 'attachment; filename=export.xls'
+    return response
index 47daec9..620696b 100644 (file)
@@ -1,12 +1,14 @@
 # -*- coding: utf-8 -*-
 from django import forms
 from django.db import connections
+from django.db.models import get_model
 from django.db.models.fields import Field
 from django.core.urlresolvers import reverse, NoReverseMatch
 from django.conf import settings
 from django.forms.formsets import BaseFormSet, formset_factory
 from django.utils.importlib import import_module
 from django.utils.translation import ugettext as _
+from django.contrib.contenttypes.models import ContentType
 
 from django_qbe.utils import get_models
 from django_qbe.widgets import CriteriaInput
@@ -128,6 +130,19 @@ class BaseQueryByExampleFormSet(BaseFormSet):
         self._sorts = sorts
         self._params = params
 
+    def translate_model_to_db_table(self, model_name):
+        """
+        Ensure the full model name match the DB table name (not app_label
+        name).
+        """
+        app_label, name = model_name.split("_")
+        try:
+            ct = ContentType.objects.get(app_label=app_label, model=name)
+            model = ct.model_class()
+            return model._meta.db_table
+        except:
+            return model_name
+
     def get_query_parts(self):
         """
         Return SQL query for cleaned data
@@ -171,8 +186,9 @@ class BaseQueryByExampleFormSet(BaseFormSet):
             if all(criteria):
                 if is_join:
                     over_split = over.lower().rsplit(".", 1)
-                    join_model = qn(over_split[0].replace(".", "_"))
+                    join_model = qn(self.translate_model_to_db_table(over_split[0].replace(".", "_")))
                     join_field = qn(over_split[1])
+                    
                     if model in self._models:
                         _field = self._models[model]._meta.get_field(field)
                         join = u"%s.%s = %s.%s" \
@@ -315,6 +331,16 @@ class BaseQueryByExampleFormSet(BaseFormSet):
         else:
             return len(self.get_results())
 
+
+    def get_model(self, db_prefix, model):
+        klass = get_model(db_prefix, model)
+        if klass is None:
+            db_model = "%s_%s" % (db_prefix, model)
+            for table in self._models.keys():
+                if table == db_model:
+                    return self._models[table]
+        return klass
+
     def get_labels(self, add_extra_ids=False, row_number=False):
         if row_number:
             labels = [_(u"#")]
@@ -326,12 +352,12 @@ class BaseQueryByExampleFormSet(BaseFormSet):
             selects = self._selects
         if selects and isinstance(selects, (tuple, list)):
             for select in selects:
-                label_splits = select.replace("_", ".").split(".")
-                label_splits_field = " ".join(label_splits[2:]).capitalize()
-                label = u"%s.%s: %s" % (label_splits[0].capitalize(),
-                                        label_splits[1].capitalize(),
-                                        label_splits_field)
-                labels.append(label)
+                label_splits = select.replace("`", "").replace("_", ".").split(".")
+                # restore underscore for fields which use it
+                label_field = "_".join(label_splits[2:])
+                model = self.get_model(label_splits[0], label_splits[1])
+                label = model._meta.get_field_by_name(label_field)[0].verbose_name
+                labels.append(label.capitalize())
         return labels
 
     def _unquote_name(self, name):
diff --git a/src/qbe/django_qbe/migrations/0001_initial.py b/src/qbe/django_qbe/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..de0d882
--- /dev/null
@@ -0,0 +1,41 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+    
+    def forwards(self, orm):
+        
+        # Adding model 'SavedQuery'
+        db.create_table('django_qbe_savedquery', (
+            ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('date_updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+            ('query_data', self.gf('picklefield.fields.PickledObjectField')()),
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
+        ))
+        db.send_create_signal('django_qbe', ['SavedQuery'])
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'SavedQuery'
+        db.delete_table('django_qbe_savedquery')
+    
+    
+    models = {
+        'django_qbe.savedquery': {
+            'Meta': {'object_name': 'SavedQuery'},
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'query_data': ('picklefield.fields.PickledObjectField', [], {})
+        }
+    }
+    
+    complete_apps = ['django_qbe']
diff --git a/src/qbe/django_qbe/migrations/0002_auto__add_field_savedquery_user_updated__add_field_savedquery_user_cre.py b/src/qbe/django_qbe/migrations/0002_auto__add_field_savedquery_user_updated__add_field_savedquery_user_cre.py
new file mode 100644 (file)
index 0000000..3946975
--- /dev/null
@@ -0,0 +1,77 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+    
+    def forwards(self, orm):
+        
+        # Adding field 'SavedQuery.user_updated'
+        db.add_column('django_qbe_savedquery', 'user_updated', self.gf('django.db.models.fields.related.ForeignKey')(default=1, related_name='savedquery_updator', to=orm['auth.User']), keep_default=False)
+
+        # Adding field 'SavedQuery.user_created'
+        db.add_column('django_qbe_savedquery', 'user_created', self.gf('django.db.models.fields.related.ForeignKey')(default=1, related_name='savedquery_creator', to=orm['auth.User']), keep_default=False)
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting field 'SavedQuery.user_updated'
+        db.delete_column('django_qbe_savedquery', 'user_updated_id')
+
+        # Deleting field 'SavedQuery.user_created'
+        db.delete_column('django_qbe_savedquery', 'user_created_id')
+    
+    
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'django_qbe.savedquery': {
+            'Meta': {'object_name': 'SavedQuery'},
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'query_data': ('picklefield.fields.PickledObjectField', [], {}),
+            'user_created': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'savedquery_creator'", 'to': "orm['auth.User']"}),
+            'user_updated': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'savedquery_updator'", 'to': "orm['auth.User']"})
+        }
+    }
+    
+    complete_apps = ['django_qbe']
diff --git a/src/qbe/django_qbe/migrations/__init__.py b/src/qbe/django_qbe/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/qbe/django_qbe/models.py b/src/qbe/django_qbe/models.py
new file mode 100644 (file)
index 0000000..46de5e0
--- /dev/null
@@ -0,0 +1,21 @@
+import pickle
+from django.contrib.auth.models import User
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from picklefield.fields import PickledObjectField
+
+class SavedQuery(models.Model):
+    name = models.CharField(_("name"), max_length=100)
+    description = models.TextField(_("description"), blank=True)
+    query_data = PickledObjectField(protocol=pickle.HIGHEST_PROTOCOL)
+    date_created = models.DateTimeField(_("date created"), auto_now_add=True)
+    date_updated = models.DateTimeField(_("date updated"), auto_now=True)
+    user_created = models.ForeignKey(User, blank=False, related_name="savedquery_creator")
+    user_updated = models.ForeignKey(User, blank=False, related_name="savedquery_updator")
+
+    class Meta:
+        verbose_name = _("Saved query")
+        verbose_name_plural = _("Saved queries")
+
+    def __unicode__(self):
+        return self.name
index ae33c24..7564825 100644 (file)
@@ -21,8 +21,8 @@ div.qbeDiagram div.body {
    -o-box-shadow: 2px 2px 5px #aaa;
    -webkit-box-shadow: 2px 2px 5px #aaa;
    -moz-box-shadow: 2px 2px 5px #aaa;
-    opacity: 0.8; 
-    filter: alpha(opacity=80); 
+    opacity: 0.8;
+    filter: alpha(opacity=80);
 }
 
 div.qbeDiagram div.body:hover {
@@ -30,7 +30,7 @@ div.qbeDiagram div.body:hover {
    -o-box-shadow: 2px 2px 5px #444;
    -webkit-box-shadow: 2px 2px 5px #444;
    -moz-box-shadow: 2px 2px 5px #444;
-    opacity: 0.95; 
+    opacity: 0.95;
     filter: alpha(opacity=95);
 }
 
@@ -69,19 +69,19 @@ option.field,div.qbeDiagram div.field {
 
 option.primary,div.qbeDiagram div.primary {
     font-weight: bold;
-    background: url("/media/django_qbe/img/key.gif") no-repeat center right #eee;
+    background: url("/static/django_qbe/img/key.gif") no-repeat center right #eee;
 }
 
 option.foreign,div.qbeDiagram div.foreign {
     font-style: italic;
-    background: url("/media/django_qbe/img/right_arrow.gif") no-repeat scroll right center #FAFAFA;
+    background: url("/static/django_qbe/img/right_arrow.gif") no-repeat scroll right center #FAFAFA;
     cursor: pointer;
 }
 
 option.many,div.qbeDiagram div.many {
     font-style: italic;
     text-decoration: underline;
-    background: url("/media/django_qbe/img/right_arrow_red.gif") no-repeat scroll right center #FAFAFA;
+    background: url("/static/django_qbe/img/right_arrow_red.gif") no-repeat scroll right center #FAFAFA;
     cursor: pointer;
 }
 
@@ -94,7 +94,7 @@ div.qbeDiagram #qbeDiagramContainer {
     overflow: auto;
     position: relative;
     width: 100%;
-    background: url("/media/django_qbe/img/grid.jpg") scroll left top #FAFAFA;
+    background: url("/static/django_qbe/img/grid.jpg") scroll left top #FAFAFA;
     margin-bottom: 0px;
 }
 
@@ -115,7 +115,7 @@ div.qbeDiagram .resizehandle {
 }
 
 
+
 /**
  * Layers
  */
index de90600..5f230bb 100644 (file)
@@ -154,11 +154,17 @@ qbe.Core = function() {};
          * Event triggered when the SELECT tag for fill models is changed
          */
         qbe.Core.fillModelsEvent = function() {
-            var appModel, key, fields, splits, appModelSplits, prefix, css, cssSplit, domTo, option, optFields, optPrimaries, optForeigns, optManies, style, value;
+            var appModel, key, fields, splits, appModelSplits, allowed_fields, prefix, css, cssSplit, domTo, option, optFields, optPrimaries, optForeigns, optManies, style, value;
             appModel = $(this).val();
             if (appModel) {
                 appModelSplits = appModel.split(".");
                 fields = qbe.Models[appModelSplits[0]][appModelSplits[1]].fields;
+                allowed_fields = null;
+                if (qbe.AllowedFields !== null) {
+                    try {
+                        allowed_fields = qbe.AllowedFields[appModelSplits[0]][appModelSplits[1]];
+                    } catch(e) {}
+                }
                 splits = $(this).attr("id").split("-");
                 prefix = splits.splice(0, splits.length-1).join("-");
                 css = $(this).attr("class");
@@ -168,23 +174,26 @@ qbe.Core = function() {};
                 optPrimaries = [];
                 optForeigns = [];
                 optManies = [];
-                for(key in fields) {
+                for(key_num in fields) {
                     // We can't jump fields with no target 'cause they are
                     // ManyToManyField and ForeignKey fields!
-                    value = fields[key].label;
-                    if (fields[key].type == "ForeignKey") {
+                    key = fields[key_num][0];
+                    value = fields[key_num][1].label;
+                    if (fields[key_num][1].type == "ForeignKey") {
                         style = "foreign";
                         option = '<option class="'+ style +'" value="'+ key +'">'+ value +'</option>'
                         optForeigns.push(option);
-                    } else if (fields[key].type == "ManyToManyField") {
+                    } else if (fields[key_num][1].type == "ManyToManyField") {
                         style = "many";
                         option = '<option class="'+ style +'" value="'+ key +'">'+ value +'</option>'
                         optManies.push(option);
-                    } else if (fields[key].primary) {
+                    } else if (fields[key_num][1].primary) {
                         style = "primary";
                         option = '<option class="'+ style +'" value="'+ key +'">'+ value +'</option>'
                         optPrimaries.push(option);
                     } else {
+                        if (allowed_fields && allowed_fields.indexOf(key) < 0)
+                            continue;
                         style = "";
                         option = '<option class="'+ style +'" value="'+ key +'">'+ value +'</option>'
                         optFields.push(option);
@@ -220,8 +229,17 @@ qbe.Core = function() {};
                     appModel = $("#"+ prefix +"-model").val();
                     appModelSplits = appModel.split(".");
                     fields = qbe.Models[appModelSplits[0]][appModelSplits[1]].fields;
-                    if (field in fields && fields[field].target && !_loadingData) {
-                        target = fields[field].target;
+                    var found = false;
+                    for (key_num in fields) {
+                        key = fields[key_num][0];
+                        if (field == key && fields[key_num][1].target) {
+                            target = fields[key_num][1].target;
+                            found = true;
+                            break;
+                        }
+
+                    }
+                    if (found && !_loadingData) {
                         if (target.through) {
                             $(this).parent().parent().children("td:last").children("a").click();
                             targetModel = qbe.Models[target.through.name][target.through.model];
index cc7a309..0e16ae9 100644 (file)
@@ -99,8 +99,9 @@ qbe.Diagram = {};
             divTitle.append(anchorDelete);
             divFields = $("<DIV>");
             countFields = 0;
-            for(fieldName in model.fields) {
-                field = model.fields[fieldName];
+            for(var key_num in model.fields) {
+                fieldName = model.fields[key_num][0];
+                field = model.fields[key_num][1];
                 divField = $("<DIV>");
                 divField.addClass("field");
                 qbe.Diagram.setLabel(divField, field.label, field.primary);
diff --git a/src/qbe/django_qbe/templates/admin/django_qbe/savedquery/change_form.html b/src/qbe/django_qbe/templates/admin/django_qbe/savedquery/change_form.html
new file mode 100644 (file)
index 0000000..e8fae73
--- /dev/null
@@ -0,0 +1,5 @@
+{% extends 'admin/change_form.html' %}
+{% load i18n %}
+{% block object-tools-items %}{{ block.super }}
+<li><a href="run/" class="runlink">{% trans "Run" %}</a></li>
+{% endblock %}
index ce69144..99a54be 100644 (file)
@@ -76,6 +76,7 @@
         <input type="submit" name="_save" class="default" value="{% trans "Run" %}">
     </div>
 
+    {% if display_databases %}
     <div >
         {% trans "Database" %}
         <select name="database_alias" id="id_database_alias">
@@ -88,6 +89,7 @@
         {% endfor %}
         </select>
     </div>
+    {% endif %}
 
 
 </div>
index 5911629..9a7d7e1 100644 (file)
@@ -6,6 +6,7 @@ if (!window.qbe) {
     var qbe = {};
 }
 qbe.Models = {% autoescape off %}{{ json_models }}{% endautoescape %};
+qbe.AllowedFields = {% autoescape off %}{{ json_allowed_fields }}{% endautoescape %};
 {% if json_data %}
 qbe.Data = {% autoescape off %}{{ json_data }}{% endautoescape %};
 {% else %}
index 7d69778..c3714eb 100644 (file)
@@ -36,6 +36,9 @@
         {% trans "Save query as" %}
     </li>
     <li>
+        <a href="{% url admin:django_qbe_savedquery_add %}?query_hash={{ query_hash }}">{% trans "permanent" %}</a>
+    </li>
+    <li>
         <a href="{% url qbe_bookmark %}?data={{ pickled }}" title="{% trans "Drag this yo your bookmarks bar to save this query" %}" id="qbeBookmarkQuery">{% trans "bookmark" %}</a>
     </li>
     {% for format in formats %}
index 7b6851a..1fbe9da 100644 (file)
@@ -56,7 +56,7 @@ def qbe_models(admin_site=None, only_admin_models=False, json=False):
             'name': field.name,
             'type': type(field).__name__,
             'blank': field.blank,
-            'label': u"%s" % field.verbose_name.lower().capitalize(),
+            'label': "%s" % field.verbose_name.lower().capitalize(),
             'primary': field.primary_key,
         }}
 
@@ -153,6 +153,8 @@ def qbe_models(admin_site=None, only_admin_models=False, json=False):
                                  # [arrowhead=normal arrowtail=normal]'
                     model = add_relation(model, field, extras=extras)
 
+        model['fields'] = sorted(model['fields'].items())
+
         app_title = app_model._meta.app_label.title().lower().capitalize()
         if app_title not in graphs:
             graphs[app_title] = {}
index ba495f7..419709d 100644 (file)
@@ -45,9 +45,13 @@ def qbe_form(request):
         else:
             json_data = simplejson.dumps(data)
     apps = get_apps()
-    models = qbe_models(admin_site=admin_site, only_admin_models=False)
+    models = getattr(settings, "QBE_CUSTOM_MODELS", None)
+    json_allowed_fields = simplejson.dumps(getattr(settings, "QBE_ALLOWED_FIELDS", None))
+    if models is None:
+        models = qbe_models(admin_site=admin_site, only_admin_models=False)
     json_models = qbe_models(admin_site=admin_site, json=True)
     admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
+    display_databases = getattr(settings, 'QBE_DISPLAY_DATABASES', True)
     return render_to_response('qbe.html',
                               {'apps': apps,
                                'models': models,
@@ -56,8 +60,10 @@ def qbe_form(request):
                                'database_alias': db_alias,
                                'title': _(u"Query by Example"),
                                'json_models': json_models,
+                               'json_allowed_fields': json_allowed_fields,
                                'json_data': json_data,
-                               'ADMIN_MEDIA_PREFIX': admin_media_prefix},
+                               'ADMIN_MEDIA_PREFIX': admin_media_prefix,
+                               'display_databases': display_databases},
                               context_instance=RequestContext(request))
 
 
index 23f1c1d..ff26e75 100644 (file)
@@ -57,3 +57,6 @@ httplib2 = 0.7.4
 
 # Added by Buildout Versions at 2012-06-01 12:21:26.475594
 setuptools = 0.6c12dev-r88846
+
+# Added by Buildout Versions at 2012-05-30 13:39:28.023002
+django-picklefield = 0.2.1