Merge branch 'master' of git://github.com/melinath/philo
authorJoseph Spiros <joseph.spiros@ithinksw.com>
Wed, 27 Oct 2010 17:36:10 +0000 (10:36 -0700)
committerJoseph Spiros <joseph.spiros@ithinksw.com>
Wed, 27 Oct 2010 17:36:10 +0000 (10:36 -0700)
* 'master' of git://github.com/melinath/philo:
  Added grappelli inlines for contentlets and attributes which become active if grappelli is installed.
  Corrected NewsletterView add_item method to join a list of author full_names instead of a QuerySet of models.
  Bugfix: corrects erratic TreeModel get_path behavior. Was returning an extra slash at the end of paths with a given root.
  Moved get_subpath and get_reverse_params prototypes into View model.

admin/base.py
admin/pages.py
contrib/penfield/models.py
models/base.py
models/nodes.py
templates/admin/philo/edit_inline/grappelli_tabular_attribute.html [new file with mode: 0644]
templates/admin/philo/edit_inline/grappelli_tabular_container.html [new file with mode: 0644]

index f4a5f2f..cb814b7 100644 (file)
@@ -1,3 +1,4 @@
+from django.conf import settings
 from django.contrib import admin
 from django.contrib.contenttypes import generic
 from philo.models import Tag, Attribute
@@ -12,12 +13,15 @@ class AttributeInline(generic.GenericTabularInline):
        ct_fk_field = 'entity_object_id'
        model = Attribute
        extra = 1
-       template = 'admin/philo/edit_inline/tabular_attribute.html'
        allow_add = True
        classes = COLLAPSE_CLASSES
        form = AttributeForm
        formset = AttributeInlineFormSet
-       exclude = ['value_object_id']
+       fields = ['key', 'value_content_type']
+       if 'grappelli' in settings.INSTALLED_APPS:
+               template = 'admin/philo/edit_inline/grappelli_tabular_attribute.html'
+       else:
+               template = 'admin/philo/edit_inline/tabular_attribute.html'
 
 
 class EntityAdmin(admin.ModelAdmin):
index 234b9d8..15b06d9 100644 (file)
@@ -1,3 +1,4 @@
+from django.conf import settings
 from django.contrib import admin
 from django import forms
 from philo.admin.base import COLLAPSE_CLASSES
@@ -13,7 +14,11 @@ class ContentletInline(admin.StackedInline):
        formset = ContentletInlineFormSet
        form = ContentletForm
        can_delete = False
-       template = 'admin/philo/edit_inline/tabular_container.html'
+       classes = ('collapse-open', 'collapse','open')
+       if 'grappelli' in settings.INSTALLED_APPS:
+               template = 'admin/philo/edit_inline/grappelli_tabular_container.html'
+       else:
+               template = 'admin/philo/edit_inline/tabular_container.html'
 
 
 class ContentReferenceInline(admin.StackedInline):
@@ -23,7 +28,11 @@ class ContentReferenceInline(admin.StackedInline):
        formset = ContentReferenceInlineFormSet
        form = ContentReferenceForm
        can_delete = False
-       template = 'admin/philo/edit_inline/tabular_container.html'
+       classes = ('collapse-open', 'collapse','open')
+       if 'grappelli' in settings.INSTALLED_APPS:
+               template = 'admin/philo/edit_inline/grappelli_tabular_container.html'
+       else:
+               template = 'admin/philo/edit_inline/tabular_container.html'
 
 
 class PageAdmin(ViewAdmin):
index 6da87a6..6550f8a 100644 (file)
@@ -421,7 +421,7 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
        def add_item(self, feed, obj, kwargs=None):
                defaults = {
                        'title': obj.title,
-                       'author_name': ', '.join(obj.authors),
+                       'author_name': ', '.join([author.get_full_name() for author in obj.authors.all()]),
                        'pubdate': obj.date,
                        'description': obj.lede or obj.full_text,
                        'categories': [tag.name for tag in obj.tags.all()]
index 05074c3..6f881a8 100644 (file)
@@ -307,22 +307,15 @@ class TreeModel(models.Model):
                return False
        
        def get_path(self, root=None, pathsep='/', field='slug'):
-               if root is not None:
-                       if not self.has_ancestor(root):
-                               raise AncestorDoesNotExist(root)
-                       path = ''
-                       parent = self
-                       while parent and parent != root:
-                               path = getattr(parent, field, '?') + pathsep + path
-                               parent = parent.parent
-                       return path
-               else:
-                       path = getattr(self, field, '?')
-                       parent = self.parent
-                       while parent and parent != root:
-                               path = getattr(parent, field, '?') + pathsep + path
-                               parent = parent.parent
-                       return path
+               if root is not None and not self.has_ancestor(root):
+                       raise AncestorDoesNotExist(root)
+               
+               path = getattr(self, field, '?')
+               parent = self.parent
+               while parent and parent != root:
+                       path = getattr(parent, field, '?') + pathsep + path
+                       parent = parent.parent
+               return path
        path = property(get_path)
        
        def __unicode__(self):
index b16521b..0ece55f 100644 (file)
@@ -5,14 +5,14 @@ from django.contrib.sites.models import Site
 from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect
 from django.core.exceptions import ViewDoesNotExist
 from django.core.servers.basehttp import FileWrapper
-from django.core.urlresolvers import resolve, clear_url_caches, reverse
+from django.core.urlresolvers import resolve, clear_url_caches, reverse, NoReverseMatch
 from django.template import add_to_builtins as register_templatetags
 from inspect import getargspec
 from philo.exceptions import MIDDLEWARE_NOT_CONFIGURED
 from philo.models.base import TreeEntity, Entity, QuerySetMapper, register_value_model
 from philo.utils import ContentTypeSubclassLimiter
 from philo.validators import RedirectValidator
-from philo.exceptions import ViewDoesNotProvideSubpaths, AncestorDoesNotExist
+from philo.exceptions import ViewCanNotProvideSubpath, ViewDoesNotProvideSubpaths, AncestorDoesNotExist
 from philo.signals import view_about_to_render, view_finished_rendering
 
 
@@ -62,7 +62,18 @@ class View(Entity):
        accepts_subpath = False
        
        def get_subpath(self, obj):
-               raise ViewDoesNotProvideSubpaths
+               if not self.accepts_subpath:
+                       raise ViewDoesNotProvideSubpaths
+               
+               view_name, args, kwargs = self.get_reverse_params(obj)
+               try:
+                       return reverse(view_name, args=args, kwargs=kwargs, urlconf=self)
+               except NoReverseMatch:
+                       raise ViewCanNotProvideSubpath
+       
+       def get_reverse_params(self, obj):
+               """This method should return a view_name, args, kwargs tuple suitable for reversing a url for the given obj using self as the urlconf."""
+               raise NotImplementedError("View subclasses must implement get_reverse_params to support subpaths.")
        
        def attributes_with_node(self, node):
                return QuerySetMapper(self.attribute_set, passthrough=node.attributes)
@@ -94,10 +105,6 @@ class MultiView(View):
        def urlpatterns(self, obj):
                raise NotImplementedError("MultiView subclasses must implement urlpatterns.")
        
-       def get_reverse_params(self, obj):
-               """This method should return a view_name, args, kwargs tuple suitable for reversing a url for the given obj using self as the urlconf."""
-               raise NotImplementedError("MultiView subclasses must implement get_subpath.")
-       
        def actually_render_to_response(self, request, extra_context=None):
                clear_url_caches()
                subpath = request.node.subpath
diff --git a/templates/admin/philo/edit_inline/grappelli_tabular_attribute.html b/templates/admin/philo/edit_inline/grappelli_tabular_attribute.html
new file mode 100644 (file)
index 0000000..4760397
--- /dev/null
@@ -0,0 +1,215 @@
+{% load i18n adminmedia %}
+
+<!-- group -->
+<div class="group tabular{% if inline_admin_formset.opts.classes %} {{ inline_admin_formset.opts.classes|join:" " }}{% endif %}"
+       id="{{ inline_admin_formset.formset.prefix }}-group" >
+       <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
+       <ul class="tools">
+               <li class="add-handler-container"><a href="javascript://" class="icon add-handler" title="{% trans 'Add Another' %}"> </a></li>
+       </ul>
+       {{ inline_admin_formset.formset.management_form }}
+       {{ inline_admin_formset.formset.non_form_errors }}
+       <!-- container -->
+       <div class="module table">
+               <div class="module thead">
+                       <div class="tr">
+                               {% for field in inline_admin_formset.fields %}
+                                       {% if not field.widget.is_hidden %}
+                                               <div class="th {{ field.label|lower }}{% if field.required %} required{% endif %}">{{ field.label|capfirst }}</div>
+                                       {% endif %}
+                               {% endfor %}
+                               <div class="th content_type">Content type</div>
+                               <div class="th value">Value</div>
+                               {% if inline_admin_formset.formset.can_delete %}<div class="th">&nbsp;</div>{% endif %}
+                       </div>
+               </div>
+               {% for inline_admin_form in inline_admin_formset %}
+                       <!-- element -->
+                       <div class="module tbody{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last %} empty-form{% endif %}"
+                               id="{{ inline_admin_formset.formset.prefix }}{% if not forloop.last %}{{ forloop.counter0 }}{% else %}-empty{% endif %}">
+                               {% if inline_admin_form.form.non_field_errors %}
+                                       <ul class="errorlist"><li>{{ inline_admin_form.form.non_field_errors }}</li></ul>
+                               {% endif %}
+                               <h3 style="display: none;"><b>{{ inline_admin_formset.opts.verbose_name|title }} #{{ forloop.counter }}</b>&nbsp;&nbsp;{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}</h3>
+                               {% spaceless %}
+                               {% for fieldset in inline_admin_form %}
+                                       {% for line in fieldset %}
+                                               {% for field in line %}
+                                                       {% if field.is_hidden %} {{ field.field }} {% endif %}
+                                               {% endfor %}
+                                       {% endfor %}
+                               {% endfor %}
+                               {% endspaceless %}
+                               <div class="tr">
+                                       {% for fieldset in inline_admin_form %}
+                                               {% for line in fieldset %}
+                                                       {% for field in line %}
+                                                               <div class="td {{ field.field.name }} {% if field.field.errors %} error{% endif %}">
+                                                                       {% if field.is_readonly %}
+                                                                               <p>{{ field.contents }}</p>
+                                                                       {% else %}
+                                                                               {{ field.field }}
+                                                                               {{ field.field.errors.as_ul }}
+                                                                       {% endif %}
+                                                               </div>
+                                                       {% endfor %}
+                                               {% endfor %}
+                                       {% endfor %}
+                                       {% with inline_admin_form.form.content_type as field %}<div class="td {{ field.name }}{% if field.errors %} error{% endif %}">{{ field }}{{ field.errors.as_ul }}</div>{% endwith %}
+                                       {% with inline_admin_form.form.value as field %}<div class="td {{ field.name }}{% if field.errors %} error{% endif %}">{{ field }}{{ field.errors.as_ul }}</div>{% endwith %}
+                                       <div class="td tools">
+                                               {% spaceless %}
+                                               <ul class="tools">
+                                                       {% if inline_admin_form.show_url %}<li class="viewsite-link-container"><a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/" class="icon viewsite-link" title="{% trans 'View on Site' %}"> </a></li>{% endif %}
+                                                       {% if inline_admin_formset.opts.sortable_field_name %}
+                                                               <li class="drag-handler-container"><a href="javascript://" class="icon drag-handler" title="{% trans 'Move Item' %}"></a></li>
+                                                       {% endif %}
+                                                       {% if inline_admin_formset.formset.can_delete %}<li class="delete-handler-container">{{ inline_admin_form.deletion_field.field }}<a href="javascript://" class="icon {% if inline_admin_form.original %}delete-handler{% else %}remove-handler{% endif %}" title="{% trans 'Remove' %}"> </a>{% endif %}
+                                               </ul>
+                                               {% endspaceless %}
+                                       </div>
+                                       {{ inline_admin_form.fk_field.field }}
+                                       {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+                               </div>
+                       </div>
+               {% endfor %}
+       </div>
+       <div class="module add-item">
+               <a href="javascript://" class="add-handler">{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}</a>
+               <ul class="tools">
+                       <li class="add-handler-container"><a href="javascript://" class="icon add-handler" title="{% trans 'Add Item' %}"> </a></li>
+               </ul><br clear="all" />
+       </div>
+</div>
+
+<script type="text/javascript">
+(function($) {
+       $(document).ready(function($) {
+               
+               $("#{{ inline_admin_formset.formset.prefix }}-group").inline({
+                       prefix: "{{ inline_admin_formset.formset.prefix }}",
+                       deleteCssClass: "delete-handler",
+                       emptyCssClass: "empty-form",
+                       onAdded: tabular_onAdded
+               });
+               
+{% if inline_admin_formset.opts.sortable_field_name %}
+               /**
+                * sortable inlines
+                * uses onAdded() and onRemoved() of inline() call above
+                * uses sortable_updateFormIndex() and is_form_filled() from change_from.html
+                */
+               
+               // hide sortable_field(_name) from form
+               // hide div.td.{{ field.name }}
+               var position_nodes = $("#{{ inline_admin_formset.formset.prefix }}-group").find("div.td.{{ inline_admin_formset.opts.sortable_field_name }}");
+               position_nodes.hide();
+               
+               // hide its header (div.th) too (hard)
+               // "div.th.{{ inline_admin_formset.opts.sortable_field_name }}" is not correct because
+               // its div.th.<field.label> (and not name, see line#18).
+               
+               // so let's get the "position/idx" the first position div
+               var tabular_row = position_nodes.first().parent().children("div.td");
+               // get the "position" (== i) in the "table"
+               for (var i = 0; i < tabular_row.length; i++) {
+                       if ($(tabular_row[i]).hasClass("{{ inline_admin_formset.opts.sortable_field_name }}")) break;
+               }
+               // we have the same order in the header of the "table"
+               // so delete the div at the "position" (== i)
+               var position_header = $("#{{ inline_admin_formset.formset.prefix }}-group").find("div.th")[i];
+               // and hide
+               $(position_header).hide()
+               
+       {% if errors %}
+               // sort inline
+               var container = $("#{{ inline_admin_formset.formset.prefix }}-group > div.table"),
+                       dynamic_forms = container.find("div.dynamic-form"),
+                       updated = false,
+                       curr_form,
+                       real_pos;
+               
+               // loop thru all inline forms
+               for (var i = 0; i < dynamic_forms.length; i++) {
+                       curr_form = $(dynamic_forms[i]);
+                       // the real position according to the sort_field(_name)
+                       real_pos = curr_form.find("div.{{ inline_admin_formset.opts.sortable_field_name }}").find("input").val();
+                       // if there is none it's an empty inline (=> we are at the end)
+                       // TODO: klemens: maybe buggy. try continue?
+                       if (!real_pos) continue;
+                       
+                       real_pos = parseInt(real_pos, 10);
+                       
+                       // check if real position is not equal to the CURRENT position in the dom
+                       if (real_pos != container.find("div.dynamic-form").index(curr_form)) {
+                               // move to correct postition
+                               curr_form.insertBefore(container.find("div.dynamic-form")[real_pos]);
+                               // to update the inline lables
+                               updated = true;
+                       }
+               }
+               
+       {% endif %}
+               
+               $("#{{ inline_admin_formset.formset.prefix }}-group > div.table").sortable({
+                       // drag&drop the inlines with the drag-handler only
+                       handle: "a.drag-handler",
+                       // very scary magic after drap&drop operations
+                       // pretty similar to inline() widget's removeHandler()
+                       // but removeHandler() can remove the current form and just reorder the rest
+                       // if we would do the same after drag&drop we would loose some form values
+                       // because while looping inputs would have the same names and maybe overwrite each other.
+                       placeholder: 'ui-sortable-placeholder',
+                       forcePlaceholderSize: true,
+                       items: "div.dynamic-form",
+                       axis: "y",
+                       start: function(evt, ui) {
+                               ui.item.hide()
+                               ui.placeholder.height(ui.placeholder.height()-4);
+                               //sadly we have to do this every time we start dragging
+                               var template = "<div class='tr'>",
+                                       // minus 1 because we don't need the "sortable_field_name row"
+                                       len = ui.item.find("div.tr").children("div.td").length - 1;
+                               
+                               for (var i = 0; i < len; i++) {
+                                       template += "<div class='td'></div>"
+                               }
+                               
+                               template += "</div>"
+                               ui.placeholder.addClass("tbody module").append(template);
+                       },
+                       update: function(evt, ui) {
+                               ui.item.show();
+                       },
+                       appendTo: 'body',
+                       forceHelperSize: true,
+                       containment: '#{{ inline_admin_formset.formset.prefix }}-group > div.table',
+                       tolerance: 'pointer',
+                       helper: function(evt, elem) {
+                               var helper = $("<div class='module table' />");
+                               helper.html(elem.clone());
+                               return helper;
+                       },
+               });
+               
+               // sets the new positions onSubmit (0 - n)
+               $("#{{ opts.module_name }}_form").bind("submit", function(){
+                       var forms = $("#{{ inline_admin_formset.formset.prefix }}-group").find("div.dynamic-form"),
+                               form,
+                               idx = 0;
+                       for (var i = 0; i < forms.length; i++) {
+                               form = $(forms[i]);
+                               
+                               if (is_form_filled(form)) {
+                                       form.find("div.{{ inline_admin_formset.opts.sortable_field_name }}").find("input").val(idx);
+                                       idx++;
+                               }
+                       }
+               });
+               
+{% endif %}
+       
+       });     
+})(django.jQuery);
+</script>
+
diff --git a/templates/admin/philo/edit_inline/grappelli_tabular_container.html b/templates/admin/philo/edit_inline/grappelli_tabular_container.html
new file mode 100644 (file)
index 0000000..5602a38
--- /dev/null
@@ -0,0 +1,43 @@
+{% load i18n adminmedia %}
+
+<!-- group -->
+{{ inline_admin_formset.formset.management_form }}
+{% comment %}Don't render the formset at all if there aren't any forms.{% endcomment %}
+{% if inline_admin_formset.formset.forms %}
+       <fieldset class="module{% if inline_admin_formset.opts.classes %} {{ inline_admin_formset.opts.classes|join:" " }}{% endif %}">
+               <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
+               {{ inline_admin_formset.formset.non_form_errors }}
+               {% for inline_admin_form in inline_admin_formset %}
+                       {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+                       {{ inline_admin_form.fk_field.field }}
+                       {% spaceless %}
+                       {% for fieldset in inline_admin_form %}
+                       {% for line in fieldset %}
+                               {% for field in line %}
+                                       {% if field.is_hidden %} {{ field.field }} {% endif %}
+                               {% endfor %}
+                       {% endfor %}
+                       {% endfor %}{% endspaceless %}
+                       <div class="row cells-{{ inline_admin_form.fields|length }}{% if not inline_admin_form.fields|length_is:"2" %} cells{% endif %}{% if inline_admin_form.errors %} errors{% endif %} {% for field in inline_admin_form %}{{ field.field.name }} {% endfor %}{% if forloop.last %} empty-form{% endif %}">
+                               <div{% if not inline_admin_form.fields|length_is:"2" %} class="cell"{% endif %}>
+                                       <div class="column span-4"><label class='required' for="{{ inline_admin_form.form.content.auto_id }}{{ inline_admin_form.form.content_id.auto_id }}">{{ inline_admin_form.form.verbose_name|capfirst }}:</label>{{ inline_admin_form.form.name.as_hidden }}</div>
+                               {% for fieldset in inline_admin_form %}{% for line in fieldset %}{% for field in line %}
+                                       {% if field.field.name != 'name' %}
+                                       <div class="column span-flexible">
+                                               {% if field.is_readonly %}
+                                                       <p class="readonly">{{ field.contents }}</p>
+                                               {% else %}
+                                                       {{ field.field }}
+                                               {% endif %}
+                                               {{ inline_admin_form.errors }}
+                                               {% if field.field.field.help_text %}
+                                                       <p class="help">{{ field.field.field.help_text|safe }}</p>
+                                               {% endif %}
+                                       </div>
+                                       {% endif %}
+                               {% endfor %}{% endfor %}{% endfor %}
+                               </div>
+                       </div>
+               {% endfor %}
+       </fieldset>
+{% endif %}