Merge branch 'cms-features'

* cms-features:
  update theme and dua
  update cms/boxes to pycon parity
  update debug toolbar
  adding in basic nav
  get object or 404 on page objects
  move cms slug down to root url
  remove menu
  add side nav with sibling pages
  rename block
  add sibling pages to context
  add django-mptt to requirements
  remove mptt driven menu
  hook up django reversion
  adding menu context processor and hooking up template
  add ordering to menus and pages
  adding prototype cms app with pages and menu
This commit is contained in:
Luke Hatcher 2012-07-11 18:36:52 -04:00
commit 0430f166fe
34 changed files with 632 additions and 5 deletions

View file

@ -23,4 +23,9 @@ django_compressor==1.2a1
-e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@8fc34ba4309fc1edd12e5836cc1398c3f9597e6d#egg=pinax-theme-bootstrap-account
-e git+git://github.com/pinax/django-user-accounts.git@fd08c676ae71d0b9d7353ddee123deb751d6ee15#egg=django-user-accounts
django-forms-bootstrap==2.0.3.post1
django-mptt==0.5.2
django-taggit==0.9.3
django-reversion==1.6.1
django-markitup==1.0.0
markdown==2.1.1
django-sitetree==0.6

View file

6
symposion/boxes/admin.py Normal file
View file

@ -0,0 +1,6 @@
from django.contrib import admin
from symposion.boxes.models import Box
admin.site.register(Box)

View file

@ -0,0 +1,20 @@
from django.conf import settings
from symposion.boxes.utils import load_path_attr
def default_can_edit(request, *args, **kwargs):
"""
This is meant to be overridden in your project per domain specific
requirements.
"""
return request.user.is_staff or request.user.is_superuser
def load_can_edit():
import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None)
if import_path is None:
return default_can_edit
return load_path_attr(import_path)

10
symposion/boxes/forms.py Normal file
View file

@ -0,0 +1,10 @@
from django import forms
from symposion.boxes.models import Box
class BoxForm(forms.ModelForm):
class Meta:
model = Box
fields = ["content"]

22
symposion/boxes/models.py Normal file
View file

@ -0,0 +1,22 @@
import datetime
from django.db import models
from django.contrib.auth.models import User
from markitup.fields import MarkupField
class Box(models.Model):
label = models.CharField(max_length=100, db_index=True)
content = MarkupField(blank=True)
created_by = models.ForeignKey(User, related_name="boxes")
last_updated_by = models.ForeignKey(User, related_name="updated_boxes")
def __unicode__(self):
return self.label
class Meta:
verbose_name_plural = "boxes"

View file

View file

@ -0,0 +1,41 @@
from django import template
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.encoding import smart_str
from django.utils.translation import ugettext_lazy as _
from django.template.defaulttags import kwarg_re
from symposion.boxes.models import Box
from symposion.boxes.forms import BoxForm
from symposion.boxes.authorization import load_can_edit
register = template.Library()
@register.inclusion_tag("boxes/box.html", takes_context=True)
def box(context, label, show_edit=True, *args, **kwargs):
request = context["request"]
can_edit = load_can_edit()(request, *args, **kwargs)
try:
box = Box.objects.get(label=label)
except Box.DoesNotExist:
box = None
if can_edit and show_edit:
form = BoxForm(instance=box, prefix=label)
form_action = reverse("box_edit", args=[label])
else:
form = None
form_action = None
return {
"request": request,
"label": label,
"box": box,
"form": form,
"form_action": form_action,
}

6
symposion/boxes/urls.py Normal file
View file

@ -0,0 +1,6 @@
from django.conf.urls.defaults import url, patterns
urlpatterns = patterns("symposion.boxes.views",
url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"),
)

19
symposion/boxes/utils.py Normal file
View file

@ -0,0 +1,19 @@
from django.core.exceptions import ImproperlyConfigured
try:
from django.utils.importlib import import_module
except ImportError:
from importlib import import_module
def load_path_attr(path):
i = path.rfind(".")
module, attr = path[:i], path[i+1:]
try:
mod = import_module(module)
except ImportError, e:
raise ImproperlyConfigured("Error importing %s: '%s'" % (module, e))
try:
attr = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured("Module '%s' does not define a '%s'" % (module, attr))
return attr

45
symposion/boxes/views.py Normal file
View file

@ -0,0 +1,45 @@
from django.http import HttpResponseForbidden
from django.shortcuts import redirect
from django.views.decorators.http import require_POST
from symposion.boxes.authorization import load_can_edit
from symposion.boxes.forms import BoxForm
from symposion.boxes.models import Box
# @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in context
def get_auth_vars(request):
auth_vars = {}
if request.method == "POST":
keys = [k for k in request.POST.keys() if k.startswith("boxes_auth_")]
for key in keys:
auth_vars[key.replace("boxes_auth_", "")] = request.POST.get(key)
auth_vars["user"] = request.user
return auth_vars
@require_POST
def box_edit(request, label):
if not load_can_edit()(request, **get_auth_vars(request)):
return HttpResponseForbidden()
next = request.GET.get("next")
try:
box = Box.objects.get(label=label)
except Box.DoesNotExist:
box = None
form = BoxForm(request.POST, instance=box, prefix=label)
if form.is_valid():
if box is None:
box = form.save(commit=False)
box.label = label
box.created_by = request.user
box.last_updated_by = request.user
box.save()
else:
form.save()
return redirect(next)

View file

6
symposion/cms/admin.py Normal file
View file

@ -0,0 +1,6 @@
from django.contrib import admin
from .models import Page
admin.site.register(Page)

16
symposion/cms/forms.py Normal file
View file

@ -0,0 +1,16 @@
from django import forms
from markitup.widgets import MarkItUpWidget
from .models import Page
class PageForm(forms.ModelForm):
class Meta:
model = Page
fields = ["title", "body", "path"]
widgets = {
"body": MarkItUpWidget(),
"path": forms.HiddenInput(),
}

View file

@ -0,0 +1,9 @@
from datetime import datetime
from django.db import models
class PublishedPageManager(models.Manager):
def get_query_set(self):
qs = super(PublishedPageManager, self).get_query_set()
return qs.filter(publish_date__lte=datetime.now())

53
symposion/cms/models.py Normal file
View file

@ -0,0 +1,53 @@
import re
from datetime import datetime
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _
from markitup.fields import MarkupField
from taggit.managers import TaggableManager
import reversion
from .managers import PublishedPageManager
class Page(models.Model):
STATUS_CHOICES = (
(1, _("Draft")),
(2, _("Public")),
)
title = models.CharField(max_length=100)
path = models.CharField(max_length=100, unique=True)
body = MarkupField()
status = models.IntegerField(choices=STATUS_CHOICES, default=2)
publish_date = models.DateTimeField(default=datetime.now)
created = models.DateTimeField(editable=False, default=datetime.now)
updated = models.DateTimeField(editable=False, default=datetime.now)
tags = TaggableManager(blank=True)
published = PublishedPageManager()
def __unicode__(self):
return self.title
@models.permalink
def get_absolute_url(self):
return ("cms_page", [self.path])
def save(self, *args, **kwargs):
self.updated = datetime.now()
super(Page, self).save(*args, **kwargs)
def clean_fields(self, exclude=None):
super(Page, self).clean_fields(exclude)
if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path):
raise ValidationError({"path": [_("Path can only contain letters, numbers and hyphens and end with /"),]})
reversion.register(Page)

8
symposion/cms/urls.py Normal file
View file

@ -0,0 +1,8 @@
from django.conf.urls.defaults import url, patterns
PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/"
urlpatterns = patterns("symposion.cms.views",
url(r"^(?P<path>%s)_edit/$" % PAGE_RE, "page_edit", name="cms_page_edit"),
url(r"^(?P<path>%s)$" % PAGE_RE, "page", name="cms_page"),
)

59
symposion/cms/views.py Normal file
View file

@ -0,0 +1,59 @@
from django.http import Http404
from django.shortcuts import render, redirect
from django.template import RequestContext
from .models import Page
from .forms import PageForm
def can_edit(user):
if user.is_staff or user.is_superuser:
return True
if user.has_perm("cms.change_page"):
return True
return False
def page(request, path):
editable = can_edit(request.user)
try:
page = Page.published.get(path=path)
except Page.DoesNotExist:
if editable:
return redirect("cms_page_edit", path=path)
else:
raise Http404
return render(request, "cms/page_detail.html", {
"page": page,
"editable": editable,
})
def page_edit(request, path):
if not can_edit(request.user):
raise Http404
try:
page = Page.published.get(path=path)
except Page.DoesNotExist:
page = None
if request.method == "POST":
form = PageForm(request.POST, instance=page)
if form.is_valid():
page = form.save(commit=False)
page.path = path
page.save()
return redirect(page)
else:
print form.errors
else:
form = PageForm(instance=page, initial={"path": path})
return render(request, "cms/page_edit.html", {
"path": path,
"form": form
})

View file

@ -106,6 +106,8 @@ MIDDLEWARE_CLASSES = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django_openid.consumer.SessionConsumer",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.transaction.TransactionMiddleware",
"reversion.middleware.RevisionMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware",
]
@ -151,7 +153,12 @@ INSTALLED_APPS = [
"django_openid",
"timezones",
"metron",
"markitup",
"taggit",
"mptt",
"reversion",
"easy_thumbnails",
"sitetree",
# Pinax
"account",
@ -160,6 +167,8 @@ INSTALLED_APPS = [
"symposion.about",
"symposion.sponsorship",
"symposion.conference",
"symposion.cms",
"symposion.boxes",
]
FIXTURE_DIRS = [
@ -192,8 +201,14 @@ DEBUG_TOOLBAR_CONFIG = {
"INTERCEPT_REDIRECTS": False,
}
MARKITUP_FILTER = ("markdown.markdown", {"safe_mode": True})
MARKITUP_SET = "markitup/sets/markdown"
MARKITUP_SKIN = "markitup/skins/simple"
CONFERENCE_ID = 1
SYMPOSION_PAGE_REGEX = r"(([\w-]{1,})(/[\w-]{1,})*)/"
# local_settings.py can be used to override environment-specific settings
# like database and email that differ between development and production.
try:

View file

@ -0,0 +1,31 @@
{% load markitup_tags %}
{% load i18n %}
{% if form %}
<div id="edit_{{ label }}" class="modal fade hide">
<form id="edit_form_{{ label }}" accept-charset="UTF-8" class="modal-form" method="POST" action="{{ form_action }}?next={{ request.path }}">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h3>{% trans "Editing content:" %} {{ label }}</h3>
</div>
<div class="modal-body">
{% csrf_token %}
{{ form.content }}
{% markitup_editor form.content.auto_id %}
</div>
<div class="modal-footer">
<a href="#" class="btn" data-dismiss="modal">Close</a>
<button type="submit" class="btn btn-primary">Save changes</a>
</div>
</form>
</div>
{% endif %}
<div id="content_{{ label }}" class="content-box {% if form %}editable{% endif %}">
{% if form %}
<a href="#edit_{{ label }}" data-toggle="modal" class="btn edit-toggle"><i class="icon-pencil"></i></a>
{% endif %}
{{ box.content|safe }}
</div>

View file

@ -0,0 +1,22 @@
{% extends "site_base.html" %}
{% load sitetree %}
{% load i18n %}
{% block body_class %}cms-page{% endblock %}
{% block head_title %}{{ page.title }}{% endblock %}
{% block page_title %}
{{ page.title }}
{% if editable %}
<div class="pull-right">
<a href="{% url cms_page_edit page.path %}" class="btn"><i class="icon-pencil icon-large"></i></a>
</div>
{% endif %}
{% endblock %}
{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %}
{% block body %}
{{ page.body }}
{% endblock %}

View file

@ -0,0 +1,22 @@
{% extends "site_base.html" %}
{% load sitetree %}
{% load i18n %}
{% load bootstrap_tags %}
{% block body_class %}cms-page{% endblock %}
{% block head_title %}Create Page{% endblock %}
{% block page_title %}{% trans "Edit page at:" %} {{ path }}{% endblock %}
{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %}
{% block body %}
<form method="POST" action="">
{% csrf_token %}
{{ form|as_bootstrap }}
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</form>
{% endblock %}

View file

@ -2,16 +2,44 @@
{% load metron_tags %}
{% load i18n %}
{% load sitetree %}
{% load markitup_tags %}
{% load static %}
{% block extra_head_base %}
<link href="{{ STATIC_URL }}img/favicon.ico" rel="shortcut icon" />
<script src="{% block jquery_src %}{% static "pinax/js/jquery.js" %}{% endblock %}"></script>
{% markitup_media "no-jquery" %}
{% block extra_head %}{% endblock %}
{% endblock %}
{% block nav %}
{% sitetree_menu from "main" include "trunk" %}
{% endblock %}
{% block body_base %}
<div class="container">
{% include "_messages.html" %}
{% block breadcrumbs %}
{% sitetree_breadcrumbs from "main" %}
{% endblock %}
{% block page_title %}
{% endblock %}
{% block body %}
{% endblock %}
</div>
{% endblock %}
{% block footer %}
{% include "_footer.html" %}
{% endblock %}
{% block extra_body_base %}
{% block script_base %}
{% analytics %}
{% block extra_body %}{% endblock %}
{% endblock %}
<script src="{% static "bootstrap/js/bootstrap.js" %}"></script>
<script src="{% static "pinax/js/theme.js" %}"></script>
{% block extra_script %}{% endblock %}
{% endblock %}

View file

@ -0,0 +1,17 @@
{% load sitetree %}
{% if sitetree_items %}
<ul class="breadcrumb">
{% for item in sitetree_items %}
{% if not forloop.last %}
<li>
<a href="{% sitetree_url for item %}" {% if item.hint %}title="{{ item.hint }}"{% endif %}>{{ item.title_resolved }}</a>
<span class="divider">/</span>
</li>
{% else %}
<li class="active">{{ item.title_resolved }}</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
{% endif %}

View file

@ -0,0 +1,16 @@
{% load sitetree %}
<ul class="nav">
{% for item in sitetree_items %}
<li class="{{ item.is_current|yesno:"active ," }}{% if item.has_children %}dropdown{% endif %}">
{% if item.has_children %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown" title="{{ item.hint|default:"" }}">
{{ item.title_resolved }}
<b class="caret"></b>
</a>
{% sitetree_children of item for menu template "sitetree/submenu.html" %}
{% else %}
<a href="{% sitetree_url for item %}" title="{{ item.hint|default:"" }}">{{ item.title_resolved }}</a>
{% endif %}
</li>
{% endfor %}
</ul>

View file

@ -0,0 +1,8 @@
{% load sitetree %}
<ul class="dropdown-menu">
{% for item in sitetree_items %}
<li>
<a href="{% sitetree_url for item %}" title="{{ item.hint|default:"" }}">{{ item.title_resolved }}</a>
</li>
{% endfor %}
</ul>

View file

@ -0,0 +1,15 @@
{% load sitetree %}
{% if sitetree_items %}
<ul>
{% for item in sitetree_items %}
{% if item.insitetree %}
<li>
<a href="{% sitetree_url for item %}" {% if item.hint %}title="{{ item.hint }}"{% endif %}>{{ item.title_resolved }}</a>
{% if item.has_children %}
{% sitetree_children of item for sitetree template "sitetree/tree.html" %}
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}

View file

@ -9,6 +9,7 @@ admin.autodiscover()
# from pinax.apps.account.openid_consumer import PinaxConsumer
WIKI_SLUG = r"(([\w-]{2,})(/[\w-]{2,})*)"
urlpatterns = patterns("",
url(r"^$", direct_to_template, {
@ -18,7 +19,12 @@ urlpatterns = patterns("",
url(r"^about/", include("symposion.about.urls")),
url(r"^account/", include("account.urls")),
# url(r"^openid/", include(PinaxConsumer().urls)),
url(r"^boxes/", include("symposion.boxes.urls")),
url(r"^markitup/", include("markitup.urls")),
url(r"^", include("symposion.cms.urls")),
)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

View file

@ -0,0 +1,12 @@
from django.contrib import admin
from mptt.admin import MPTTModelAdmin
from cms.models import Page
class PageAdmin(MPTTModelAdmin):
prepopulated_fields = {"slug": ("title",)}
list_display = ("title", "published", "status")
admin.site.register(Page, PageAdmin)

View file

@ -0,0 +1,65 @@
from datetime import datetime
from django.db import models
from django.utils.translation import ugettext_lazy as _
from markitup.fields import MarkupField
from taggit.managers import TaggableManager
from mptt.models import MPTTModel, TreeForeignKey
from mptt.utils import drilldown_tree_for_node
import reversion
class ContentBase(models.Model):
STATUS_CHOICES = (
(1, _("Draft")),
(2, _("Public")),
)
title = models.CharField(max_length=100)
slug = models.CharField(max_length=100, blank=True, null=True)
body = MarkupField()
tags = TaggableManager(blank=True)
status = models.IntegerField(choices=STATUS_CHOICES, default=2)
published = models.DateTimeField(default=datetime.now)
created = models.DateTimeField(editable=False, default=datetime.now)
updated = models.DateTimeField(editable=False, default=datetime.now)
class Meta:
abstract = True
class Page(MPTTModel, ContentBase):
parent = TreeForeignKey("self", null=True, blank=True, related_name="children")
ordering = models.PositiveIntegerField(default=1)
path = models.TextField(blank=True, editable=False)
def __unicode__(self):
return self.title
def save(self, calculate_path=True, *args, **kwargs):
super(Page, self).save(*args, **kwargs)
if calculate_path:
self.calculate_path()
def calculate_path(self):
self.path = ""
for page in drilldown_tree_for_node(self):
if page == self:
self.path += page.slug
break
else:
self.path += "%s/" % page.slug
self.save(calculate_path=False)
class MPTTMeta:
order_insertion_by = ["ordering", "title"]
reversion.register(Page)

View file

@ -0,0 +1,15 @@
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from cms.models import Page
def page(request, slug):
page = get_object_or_404(Page, path=slug)
siblings = page.get_siblings(include_self=True)
return render_to_response("cms/page_detail.html", {
"page": page,
"siblings": siblings,
}, context_instance=RequestContext(request))

View file

@ -0,0 +1,19 @@
{% extends "subnav_base.html" %}
{% block subnav %}
<ul class="nav nav-list">
<li class="nav-header">{{ page.parent }}</li>
{% for sibling in siblings %}
{% if sibling == page %}
<li>{{ sibling }}</li>
{% else %}
<li><a href="{% url cms_page sibling.path %}">{{ sibling }}</a>
{% endif %}
{% endfor %}
</ul>
{% endblock %}
{% block body %}
<h1>{{ page.title }}</h1>
{{ page.body }}
{% endblock %}

View file

@ -0,0 +1,11 @@
{% load sitetree %}
<ul class="nav">
{% for item in sitetree_items %}
<li class="{{ item.is_current|yesno:"active ," }}">
<a href="{% sitetree_url for item %}" title="{{ item.hint|default:"" }}">{{ item.title_resolved }}</a>
{% if item.has_children %}
{% sitetree_children of item for menu template "sitetree/menu.html" %}
{% endif %}
</li>
{% endfor %}
</ul>