Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

update cms/boxes to pycon parity

  • Loading branch information...
commit 2665fd575869fa363a95958474ffd9453c500b7c 1 parent 1b04b7a
@lukeman lukeman authored
Showing with 442 additions and 82 deletions.
  1. +1 −1  requirements/base.txt
  2. 0  symposion/boxes/__init__.py
  3. +6 −0 symposion/boxes/admin.py
  4. +20 −0 symposion/boxes/authorization.py
  5. +10 −0 symposion/boxes/forms.py
  6. +22 −0 symposion/boxes/models.py
  7. 0  symposion/boxes/templatetags/__init__.py
  8. +41 −0 symposion/boxes/templatetags/boxes_tags.py
  9. +6 −0 symposion/boxes/urls.py
  10. +19 −0 symposion/boxes/utils.py
  11. +45 −0 symposion/boxes/views.py
  12. +2 −8 symposion/cms/admin.py
  13. +16 −0 symposion/cms/forms.py
  14. +9 −0 symposion/cms/managers.py
  15. +24 −36 symposion/cms/models.py
  16. +8 −0 symposion/cms/urls.py
  17. +52 −8 symposion/cms/views.py
  18. +7 −1 symposion/settings.py
  19. +31 −0 symposion/templates/boxes/box.html
  20. +16 −13 symposion/templates/cms/page_detail.html
  21. +22 −0 symposion/templates/cms/page_edit.html
  22. +27 −4 symposion/templates/site_base.html
  23. +17 −0 symposion/templates/sitetree/breadcrumbs.html
  24. +14 −9 symposion/templates/sitetree/menu.html
  25. +8 −0 symposion/templates/sitetree/submenu.html
  26. +15 −0 symposion/templates/sitetree/tree.html
  27. +4 −2 symposion/urls.py
View
2  requirements/base.txt
@@ -25,7 +25,7 @@ django_compressor==1.2a1
django-mptt==0.5.2
django-taggit==0.9.3
-django-reversion==1.5.1
+django-reversion==1.6.1
django-markitup==1.0.0
markdown==2.1.1
django-sitetree==0.6
View
0  symposion/boxes/__init__.py
No changes.
View
6 symposion/boxes/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+
+from symposion.boxes.models import Box
+
+
+admin.site.register(Box)
View
20 symposion/boxes/authorization.py
@@ -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)
View
10 symposion/boxes/forms.py
@@ -0,0 +1,10 @@
+from django import forms
+
+from symposion.boxes.models import Box
+
+
+class BoxForm(forms.ModelForm):
+
+ class Meta:
+ model = Box
+ fields = ["content"]
View
22 symposion/boxes/models.py
@@ -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
0  symposion/boxes/templatetags/__init__.py
No changes.
View
41 symposion/boxes/templatetags/boxes_tags.py
@@ -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,
+ }
View
6 symposion/boxes/urls.py
@@ -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"),
+)
View
19 symposion/boxes/utils.py
@@ -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
View
45 symposion/boxes/views.py
@@ -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
10 symposion/cms/admin.py
@@ -1,12 +1,6 @@
from django.contrib import admin
-from mptt.admin import MPTTModelAdmin
+from .models import Page
-from cms.models import Page
-
-class PageAdmin(MPTTModelAdmin):
- prepopulated_fields = {"slug": ("title",)}
- list_display = ("title", "published", "status")
-
-admin.site.register(Page, PageAdmin)
+admin.site.register(Page)
View
16 symposion/cms/forms.py
@@ -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
9 symposion/cms/managers.py
@@ -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
60 symposion/cms/models.py
@@ -1,5 +1,8 @@
+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 _
@@ -7,59 +10,44 @@
from taggit.managers import TaggableManager
-from mptt.models import MPTTModel, TreeForeignKey
-from mptt.utils import drilldown_tree_for_node
-
import reversion
+from .managers import PublishedPageManager
+
-class ContentBase(models.Model):
+class Page(models.Model):
STATUS_CHOICES = (
(1, _("Draft")),
(2, _("Public")),
)
-
+
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()
-
- tags = TaggableManager(blank=True)
-
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)
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)
-
+ tags = TaggableManager(blank=True)
+
+ published = PublishedPageManager()
+
def __unicode__(self):
return self.title
-
- def save(self, calculate_path=True, *args, **kwargs):
+
+ @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)
- 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)
+ 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)
View
8 symposion/cms/urls.py
@@ -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
60 symposion/cms/views.py
@@ -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 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)
- siblings = page.get_siblings(include_self=True)
+ 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_to_response("cms/page_detail.html", {
+ return render(request, "cms/page_detail.html", {
"page": page,
- "siblings": siblings,
- }, context_instance=RequestContext(request))
+ "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
8 symposion/settings.py
@@ -155,7 +155,6 @@
"metron",
"markitup",
"taggit",
- "cms",
"mptt",
"reversion",
"easy_thumbnails",
@@ -168,6 +167,8 @@
"symposion.about",
"symposion.sponsorship",
"symposion.conference",
+ "symposion.cms",
+ "symposion.boxes",
]
FIXTURE_DIRS = [
@@ -201,8 +202,13 @@
}
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
31 symposion/templates/boxes/box.html
@@ -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
29 symposion/templates/cms/page_detail.html
@@ -1,19 +1,22 @@
-{% extends "subnav_base.html" %}
+{% extends "site_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>
+{% 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 %}
- <h1>{{ page.title }}</h1>
{{ page.body }}
{% endblock %}
View
22 symposion/templates/cms/page_edit.html
@@ -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
31 symposion/templates/site_base.html
@@ -3,20 +3,43 @@
{% 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 "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 %}
{% block footer %}
{% include "_footer.html" %}
{% endblock %}
-{% block extra_script %}
+{% 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
17 symposion/templates/sitetree/breadcrumbs.html
@@ -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
23 symposion/templates/sitetree/menu.html
@@ -1,11 +1,16 @@
{% 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>
+ {% 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
8 symposion/templates/sitetree/submenu.html
@@ -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
15 symposion/templates/sitetree/tree.html
@@ -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
6 symposion/urls.py
@@ -20,9 +20,11 @@
url(r"^account/", include("account.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"^", include("symposion.cms.urls")),
)
-urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Please sign in to comment.
Something went wrong with that request. Please try again.