update cms/boxes to pycon parity

This commit is contained in:
Luke Hatcher 2012-07-10 18:18:48 -04:00
parent 1b04b7a9f7
commit 2665fd5758
27 changed files with 443 additions and 83 deletions

View file

@ -25,7 +25,7 @@ django_compressor==1.2a1
django-mptt==0.5.2 django-mptt==0.5.2
django-taggit==0.9.3 django-taggit==0.9.3
django-reversion==1.5.1 django-reversion==1.6.1
django-markitup==1.0.0 django-markitup==1.0.0
markdown==2.1.1 markdown==2.1.1
django-sitetree==0.6 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

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

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())

View file

@ -1,5 +1,8 @@
import re
from datetime import datetime from datetime import datetime
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -7,59 +10,44 @@ from markitup.fields import MarkupField
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from mptt.models import MPTTModel, TreeForeignKey
from mptt.utils import drilldown_tree_for_node
import reversion import reversion
from .managers import PublishedPageManager
class ContentBase(models.Model):
class Page(models.Model):
STATUS_CHOICES = ( STATUS_CHOICES = (
(1, _("Draft")), (1, _("Draft")),
(2, _("Public")), (2, _("Public")),
) )
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
slug = models.CharField(max_length=100, blank=True, null=True) path = models.CharField(max_length=100, unique=True)
body = MarkupField() body = MarkupField()
tags = TaggableManager(blank=True)
status = models.IntegerField(choices=STATUS_CHOICES, default=2) status = models.IntegerField(choices=STATUS_CHOICES, default=2)
published = models.DateTimeField(default=datetime.now) publish_date = models.DateTimeField(default=datetime.now)
created = models.DateTimeField(editable=False, default=datetime.now) created = models.DateTimeField(editable=False, default=datetime.now)
updated = models.DateTimeField(editable=False, default=datetime.now) updated = models.DateTimeField(editable=False, default=datetime.now)
tags = TaggableManager(blank=True)
class Meta:
abstract = True published = PublishedPageManager()
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): def __unicode__(self):
return self.title 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): @models.permalink
self.path = "" def get_absolute_url(self):
for page in drilldown_tree_for_node(self): return ("cms_page", [self.path])
if page == self:
self.path += page.slug def save(self, *args, **kwargs):
break self.updated = datetime.now()
else: super(Page, self).save(*args, **kwargs)
self.path += "%s/" % page.slug
self.save(calculate_path=False) 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 /"),]})
class MPTTMeta:
order_insertion_by = ["ordering", "title"]
reversion.register(Page) 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"),
)

View file

@ -1,15 +1,59 @@
from django.shortcuts import render_to_response, get_object_or_404 from django.http import Http404
from django.shortcuts import render, redirect
from django.template import RequestContext from django.template import RequestContext
from cms.models import Page from .models import Page
from .forms import PageForm
def page(request, slug): 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):
page = get_object_or_404(Page, path=slug) editable = can_edit(request.user)
siblings = page.get_siblings(include_self=True) try:
page = Page.published.get(path=path)
except Page.DoesNotExist:
if editable:
return redirect("cms_page_edit", path=path)
else:
raise Http404
return render_to_response("cms/page_detail.html", { return render(request, "cms/page_detail.html", {
"page": page, "page": page,
"siblings": siblings, "editable": editable,
}, context_instance=RequestContext(request)) })
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

@ -155,7 +155,6 @@ INSTALLED_APPS = [
"metron", "metron",
"markitup", "markitup",
"taggit", "taggit",
"cms",
"mptt", "mptt",
"reversion", "reversion",
"easy_thumbnails", "easy_thumbnails",
@ -168,6 +167,8 @@ INSTALLED_APPS = [
"symposion.about", "symposion.about",
"symposion.sponsorship", "symposion.sponsorship",
"symposion.conference", "symposion.conference",
"symposion.cms",
"symposion.boxes",
] ]
FIXTURE_DIRS = [ FIXTURE_DIRS = [
@ -201,8 +202,13 @@ DEBUG_TOOLBAR_CONFIG = {
} }
MARKITUP_FILTER = ("markdown.markdown", {"safe_mode": True}) MARKITUP_FILTER = ("markdown.markdown", {"safe_mode": True})
MARKITUP_SET = "markitup/sets/markdown"
MARKITUP_SKIN = "markitup/skins/simple"
CONFERENCE_ID = 1 CONFERENCE_ID = 1
SYMPOSION_PAGE_REGEX = r"(([\w-]{1,})(/[\w-]{1,})*)/"
# local_settings.py can be used to override environment-specific settings # local_settings.py can be used to override environment-specific settings
# like database and email that differ between development and production. # like database and email that differ between development and production.
try: 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

@ -1,19 +1,22 @@
{% extends "subnav_base.html" %} {% extends "site_base.html" %}
{% block subnav %} {% load sitetree %}
<ul class="nav nav-list"> {% load i18n %}
<li class="nav-header">{{ page.parent }}</li>
{% for sibling in siblings %} {% block body_class %}cms-page{% endblock %}
{% if sibling == page %}
<li>{{ sibling }}</li> {% block head_title %}{{ page.title }}{% endblock %}
{% else %}
<li><a href="{% url cms_page sibling.path %}">{{ sibling }}</a> {% block page_title %}
{% endif %} {{ page.title }}
{% endfor %} {% if editable %}
</ul> <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 %} {% endblock %}
{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %}
{% block body %} {% block body %}
<h1>{{ page.title }}</h1>
{{ page.body }} {{ page.body }}
{% endblock %} {% 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

@ -3,20 +3,43 @@
{% load metron_tags %} {% load metron_tags %}
{% load i18n %} {% load i18n %}
{% load sitetree %} {% load sitetree %}
{% load markitup_tags %}
{% load static %}
{% block extra_head_base %} {% 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 %} {% block extra_head %}{% endblock %}
{% endblock %} {% endblock %}
{% block nav %} {% block nav %}
{% sitetree_menu from "root" include "trunk" %} {% 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 %} {% endblock %}
{% block footer %} {% block footer %}
{% include "_footer.html" %} {% include "_footer.html" %}
{% endblock %} {% endblock %}
{% block extra_script %} {% block script_base %}
{% analytics %} {% analytics %}
{% block extra_body %}{% endblock %} <script src="{% static "bootstrap/js/bootstrap.js" %}"></script>
{% endblock %} <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

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

@ -20,9 +20,11 @@ urlpatterns = patterns("",
url(r"^account/", include("account.urls")), url(r"^account/", include("account.urls")),
# url(r"^openid/", include(PinaxConsumer().urls)), # url(r"^openid/", include(PinaxConsumer().urls)),
url(r"^(?P<slug>%s)/$" % WIKI_SLUG, "cms.views.page", name="cms_page"), url(r"^boxes/", include("symposion.boxes.urls")),
url(r"^markitup/", include("markitup.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)