From 2665fd575869fa363a95958474ffd9453c500b7c Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 10 Jul 2012 18:18:48 -0400 Subject: [PATCH] update cms/boxes to pycon parity --- requirements/base.txt | 2 +- symposion/boxes/__init__.py | 0 symposion/boxes/admin.py | 6 ++ symposion/boxes/authorization.py | 20 ++++++ symposion/boxes/forms.py | 10 +++ symposion/boxes/models.py | 22 +++++++ symposion/boxes/templatetags/__init__.py | 0 symposion/boxes/templatetags/boxes_tags.py | 41 ++++++++++++ symposion/boxes/urls.py | 6 ++ symposion/boxes/utils.py | 19 ++++++ symposion/boxes/views.py | 45 ++++++++++++++ symposion/cms/admin.py | 10 +-- symposion/cms/forms.py | 16 +++++ symposion/cms/managers.py | 9 +++ symposion/cms/models.py | 62 ++++++++----------- symposion/cms/urls.py | 8 +++ symposion/cms/views.py | 60 +++++++++++++++--- symposion/settings.py | 8 ++- symposion/templates/boxes/box.html | 31 ++++++++++ symposion/templates/cms/page_detail.html | 29 +++++---- symposion/templates/cms/page_edit.html | 22 +++++++ symposion/templates/site_base.html | 31 ++++++++-- symposion/templates/sitetree/breadcrumbs.html | 17 +++++ symposion/templates/sitetree/menu.html | 23 ++++--- symposion/templates/sitetree/submenu.html | 8 +++ symposion/templates/sitetree/tree.html | 15 +++++ symposion/urls.py | 6 +- 27 files changed, 443 insertions(+), 83 deletions(-) create mode 100644 symposion/boxes/__init__.py create mode 100644 symposion/boxes/admin.py create mode 100644 symposion/boxes/authorization.py create mode 100644 symposion/boxes/forms.py create mode 100644 symposion/boxes/models.py create mode 100644 symposion/boxes/templatetags/__init__.py create mode 100644 symposion/boxes/templatetags/boxes_tags.py create mode 100644 symposion/boxes/urls.py create mode 100644 symposion/boxes/utils.py create mode 100644 symposion/boxes/views.py create mode 100644 symposion/cms/forms.py create mode 100644 symposion/cms/managers.py create mode 100644 symposion/cms/urls.py create mode 100644 symposion/templates/boxes/box.html create mode 100644 symposion/templates/cms/page_edit.html create mode 100644 symposion/templates/sitetree/breadcrumbs.html create mode 100644 symposion/templates/sitetree/submenu.html create mode 100644 symposion/templates/sitetree/tree.html diff --git a/requirements/base.txt b/requirements/base.txt index 99d7a078..d315f8f7 100644 --- a/requirements/base.txt +++ b/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 diff --git a/symposion/boxes/__init__.py b/symposion/boxes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/boxes/admin.py b/symposion/boxes/admin.py new file mode 100644 index 00000000..a6ac989a --- /dev/null +++ b/symposion/boxes/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from symposion.boxes.models import Box + + +admin.site.register(Box) \ No newline at end of file diff --git a/symposion/boxes/authorization.py b/symposion/boxes/authorization.py new file mode 100644 index 00000000..011e215d --- /dev/null +++ b/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) diff --git a/symposion/boxes/forms.py b/symposion/boxes/forms.py new file mode 100644 index 00000000..b5ad3b32 --- /dev/null +++ b/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"] diff --git a/symposion/boxes/models.py b/symposion/boxes/models.py new file mode 100644 index 00000000..85500f63 --- /dev/null +++ b/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" diff --git a/symposion/boxes/templatetags/__init__.py b/symposion/boxes/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/boxes/templatetags/boxes_tags.py b/symposion/boxes/templatetags/boxes_tags.py new file mode 100644 index 00000000..e300fd3b --- /dev/null +++ b/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, + } diff --git a/symposion/boxes/urls.py b/symposion/boxes/urls.py new file mode 100644 index 00000000..dc57fe6b --- /dev/null +++ b/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"), +) \ No newline at end of file diff --git a/symposion/boxes/utils.py b/symposion/boxes/utils.py new file mode 100644 index 00000000..24b95c69 --- /dev/null +++ b/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 diff --git a/symposion/boxes/views.py b/symposion/boxes/views.py new file mode 100644 index 00000000..b314b07e --- /dev/null +++ b/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) diff --git a/symposion/cms/admin.py b/symposion/cms/admin.py index ded416ad..729a50a2 100644 --- a/symposion/cms/admin.py +++ b/symposion/cms/admin.py @@ -1,12 +1,6 @@ from django.contrib import admin -from mptt.admin import MPTTModelAdmin - -from cms.models import Page +from .models import Page -class PageAdmin(MPTTModelAdmin): - prepopulated_fields = {"slug": ("title",)} - list_display = ("title", "published", "status") - -admin.site.register(Page, PageAdmin) +admin.site.register(Page) diff --git a/symposion/cms/forms.py b/symposion/cms/forms.py new file mode 100644 index 00000000..bbcd31ab --- /dev/null +++ b/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(), + } diff --git a/symposion/cms/managers.py b/symposion/cms/managers.py new file mode 100644 index 00000000..c8381ebd --- /dev/null +++ b/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()) diff --git a/symposion/cms/models.py b/symposion/cms/models.py index 5d07b538..d1e9668c 100644 --- a/symposion/cms/models.py +++ b/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 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 +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): - 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) + @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 /"),]}) - class MPTTMeta: - order_insertion_by = ["ordering", "title"] reversion.register(Page) diff --git a/symposion/cms/urls.py b/symposion/cms/urls.py new file mode 100644 index 00000000..ecb2ad5e --- /dev/null +++ b/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%s)_edit/$" % PAGE_RE, "page_edit", name="cms_page_edit"), + url(r"^(?P%s)$" % PAGE_RE, "page", name="cms_page"), +) diff --git a/symposion/cms/views.py b/symposion/cms/views.py index 478d2cee..5592d429 100644 --- a/symposion/cms/views.py +++ b/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 + }) diff --git a/symposion/settings.py b/symposion/settings.py index 9b3a5b80..f0815a34 100644 --- a/symposion/settings.py +++ b/symposion/settings.py @@ -155,7 +155,6 @@ INSTALLED_APPS = [ "metron", "markitup", "taggit", - "cms", "mptt", "reversion", "easy_thumbnails", @@ -168,6 +167,8 @@ INSTALLED_APPS = [ "symposion.about", "symposion.sponsorship", "symposion.conference", + "symposion.cms", + "symposion.boxes", ] FIXTURE_DIRS = [ @@ -201,8 +202,13 @@ DEBUG_TOOLBAR_CONFIG = { } 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: diff --git a/symposion/templates/boxes/box.html b/symposion/templates/boxes/box.html new file mode 100644 index 00000000..e65393ae --- /dev/null +++ b/symposion/templates/boxes/box.html @@ -0,0 +1,31 @@ +{% load markitup_tags %} +{% load i18n %} + +{% if form %} + +{% endif %} + +
+ {% if form %} + + {% endif %} + {{ box.content|safe }} +
diff --git a/symposion/templates/cms/page_detail.html b/symposion/templates/cms/page_detail.html index fb9e336e..cc13647e 100644 --- a/symposion/templates/cms/page_detail.html +++ b/symposion/templates/cms/page_detail.html @@ -1,19 +1,22 @@ -{% extends "subnav_base.html" %} +{% extends "site_base.html" %} -{% block subnav %} - +{% load sitetree %} +{% load i18n %} + +{% block body_class %}cms-page{% endblock %} + +{% block head_title %}{{ page.title }}{% endblock %} + +{% block page_title %} + {{ page.title }} + {% if editable %} +
+ +
+ {% endif %} {% endblock %} +{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} {% block body %} -

{{ page.title }}

{{ page.body }} {% endblock %} \ No newline at end of file diff --git a/symposion/templates/cms/page_edit.html b/symposion/templates/cms/page_edit.html new file mode 100644 index 00000000..e361ecdf --- /dev/null +++ b/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 %} +
+ {% csrf_token %} + {{ form|as_bootstrap }} +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/symposion/templates/site_base.html b/symposion/templates/site_base.html index 5924007d..64b95710 100644 --- a/symposion/templates/site_base.html +++ b/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 %} + + + {% 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 %} +
+ {% include "_messages.html" %} + + {% block breadcrumbs %} + {% sitetree_breadcrumbs from "main" %} + {% endblock %} + + {% block page_title %} + {% endblock %} + + {% block body %} + {% endblock %} +
{% endblock %} {% block footer %} {% include "_footer.html" %} {% endblock %} -{% block extra_script %} +{% block script_base %} {% analytics %} - {% block extra_body %}{% endblock %} -{% endblock %} \ No newline at end of file + + + {% block extra_script %}{% endblock %} +{% endblock %} diff --git a/symposion/templates/sitetree/breadcrumbs.html b/symposion/templates/sitetree/breadcrumbs.html new file mode 100644 index 00000000..3f292f2e --- /dev/null +++ b/symposion/templates/sitetree/breadcrumbs.html @@ -0,0 +1,17 @@ +{% load sitetree %} +{% if sitetree_items %} + +{% else %} + +{% endif %} diff --git a/symposion/templates/sitetree/menu.html b/symposion/templates/sitetree/menu.html index 93a50ea8..71e6ac1b 100644 --- a/symposion/templates/sitetree/menu.html +++ b/symposion/templates/sitetree/menu.html @@ -1,11 +1,16 @@ {% load sitetree %} \ No newline at end of file + {% for item in sitetree_items %} +
  • + {% if item.has_children %} + + {{ item.title_resolved }} + + + {% sitetree_children of item for menu template "sitetree/submenu.html" %} + {% else %} + {{ item.title_resolved }} + {% endif %} +
  • + {% endfor %} + diff --git a/symposion/templates/sitetree/submenu.html b/symposion/templates/sitetree/submenu.html new file mode 100644 index 00000000..93684588 --- /dev/null +++ b/symposion/templates/sitetree/submenu.html @@ -0,0 +1,8 @@ +{% load sitetree %} + diff --git a/symposion/templates/sitetree/tree.html b/symposion/templates/sitetree/tree.html new file mode 100644 index 00000000..e7a1001f --- /dev/null +++ b/symposion/templates/sitetree/tree.html @@ -0,0 +1,15 @@ +{% load sitetree %} +{% if sitetree_items %} +
      + {% for item in sitetree_items %} + {% if item.insitetree %} +
    • + {{ item.title_resolved }} + {% if item.has_children %} + {% sitetree_children of item for sitetree template "sitetree/tree.html" %} + {% endif %} +
    • + {% endif %} + {% endfor %} +
    +{% endif %} diff --git a/symposion/urls.py b/symposion/urls.py index 8a8a6f9f..cd922680 100644 --- a/symposion/urls.py +++ b/symposion/urls.py @@ -20,9 +20,11 @@ urlpatterns = patterns("", url(r"^account/", include("account.urls")), # url(r"^openid/", include(PinaxConsumer().urls)), - url(r"^(?P%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) \ No newline at end of file +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)