add basic views to create/edit/delete notes
authorFrédéric Péters <fpeters@0d.be>
Sun, 29 Dec 2019 14:51:28 +0000 (15:51 +0100)
committerFrédéric Péters <fpeters@0d.be>
Mon, 30 Dec 2019 08:35:50 +0000 (09:35 +0100)
13 files changed:
chloro/monkeypatch.py [new file with mode: 0644]
chloro/phyll/__init__.py
chloro/phyll/apps.py [new file with mode: 0644]
chloro/phyll/fields.py
chloro/phyll/models.py
chloro/phyll/templates/phyll/base.html
chloro/phyll/templates/phyll/note_confirm_delete.html [new file with mode: 0644]
chloro/phyll/templates/phyll/note_detail.html
chloro/phyll/templates/phyll/note_form.html [new file with mode: 0644]
chloro/phyll/urls.py
chloro/phyll/views.py
chloro/settings.py
setup.py

diff --git a/chloro/monkeypatch.py b/chloro/monkeypatch.py
new file mode 100644 (file)
index 0000000..436b0cd
--- /dev/null
@@ -0,0 +1,83 @@
+# combo - content management system
+# Copyright (C) 2018  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.core.urlresolvers import reverse
+from django.forms.utils import flatatt
+from django.template.loader import render_to_string
+from django.utils.encoding import force_text
+from django.utils.html import conditional_escape
+from django.utils.safestring import mark_safe
+from django.utils.translation import get_language
+
+import ckeditor.views
+import ckeditor.widgets
+from ckeditor.image import pillow_backend
+
+
+def ckeditor_render(self, name, value, attrs=None):
+    if value is None:
+        value = ''
+    final_attrs = {'name': name}
+    if getattr(self, 'attrs', None):
+        final_attrs.update(self.attrs)
+    if attrs:
+        final_attrs.update(attrs)
+    if 'filebrowserUploadUrl' not in self.config:
+        self.config.setdefault('filebrowserUploadUrl', reverse('ckeditor_upload'))
+    if 'filebrowserBrowseUrl' not in self.config:
+        self.config.setdefault('filebrowserBrowseUrl', reverse('ckeditor_browse'))
+    if not self.config.get('language'):
+        self.config['language'] = get_language()
+
+    # Force to text to evaluate possible lazy objects
+    external_plugin_resources = [
+        [force_text(a), force_text(b), force_text(c)] for a, b, c in self.external_plugin_resources
+    ]
+
+    return mark_safe(
+        render_to_string(
+            'ckeditor/widget.html',
+            {
+                'final_attrs': flatatt(final_attrs),
+                'value': conditional_escape(force_text(value)),
+                'id': final_attrs['id'],
+                'config': ckeditor.widgets.json_encode(self.config),
+                'external_plugin_resources': ckeditor.widgets.json_encode(external_plugin_resources),
+            },
+        )
+    )
+
+
+ckeditor.widgets.CKEditorWidget.render = ckeditor_render
+
+orig_should_create_thumbnail = pillow_backend.should_create_thumbnail
+
+
+def should_create_thumbnail(file_path):
+    if file_path.endswith('.gif'):
+        # disable thumbnails for gif to workaround
+        # https://github.com/python-pillow/Pillow/issues/2803
+        return False
+    return orig_should_create_thumbnail(file_path)
+
+
+def get_backend():
+    backend = pillow_backend
+    backend.should_create_thumbnail = should_create_thumbnail
+    return backend
+
+
+ckeditor.views.image_processing.get_backend = get_backend
index e69de29..93e2837 100644 (file)
@@ -0,0 +1,17 @@
+# chloro - personal space
+# Copyright (C) 2019  Frederic Peters
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+default_app_config = 'chloro.phyll.apps.AppConfig'
diff --git a/chloro/phyll/apps.py b/chloro/phyll/apps.py
new file mode 100644 (file)
index 0000000..3722988
--- /dev/null
@@ -0,0 +1,24 @@
+# chloro - personal space
+# Copyright (C) 2019  Frederic Peters
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.apps import AppConfig
+
+
+class AppConfig(AppConfig):
+    name = 'chloro.phyll'
+
+    def ready(self):
+        import chloro.monkeypatch
index 8440e4b..2ce4b91 100644 (file)
@@ -14,6 +14,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from django.conf import settings
 
 import ckeditor.fields
 
index 587859d..b8872c4 100644 (file)
@@ -28,3 +28,6 @@ class Note(models.Model):
     tags = TaggableManager(_('Tags'), blank=True)
     creation_timestamp = models.DateTimeField(auto_now_add=True)
     last_update_timestamp = models.DateTimeField(auto_now=True)
+
+    def get_absolute_url(self):
+        return '/%s/' % self.slug
index 466fb4b..5796945 100644 (file)
@@ -1,13 +1,22 @@
-<!DOCTYPE html>
+{% load i18n %}<!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8"/> <!-- 🌱 -->
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Coin web de Frédéric Péters</title>
     <link rel="stylesheet" type="text/css" href="/static/css/style.css">
+    {% block bottom-head %}
+    {% endblock %}
   </head>
   <body>
   {% block body %}
   {% endblock %}
+  {% if request.user.is_staff %}
+  <div class="actions">
+    <a href="/new-note/">{% trans "New Note" %}</a>
+    {% block bottom-actions %}
+    {% endblock %}
+  </div>
+  {% endif %}
   </body>
 </html>
diff --git a/chloro/phyll/templates/phyll/note_confirm_delete.html b/chloro/phyll/templates/phyll/note_confirm_delete.html
new file mode 100644 (file)
index 0000000..0f5ca8e
--- /dev/null
@@ -0,0 +1,10 @@
+{% extends "phyll/base.html" %}
+{% load i18n %}
+
+{% block body %}
+<form method="post">
+{% csrf_token %}
+{% trans "Delete?" %}
+<button>{% trans "Delete" %}</button>
+</form>
+{% endblock %}
index 4abf3af..f9d1b01 100644 (file)
@@ -1,8 +1,12 @@
 {% extends "phyll/base.html" %}
+{% load i18n %}
 
 {% block body %}
 <h1>{{ object.title }}</h1>
-
 <div>{{ object.text|safe }}</div>
+{% endblock %}
 
+{% block bottom-actions %}
+<a href="edit/">{% trans "Edit" %}</a>
+<a href="delete/">{% trans "Delete" %}</a>
 {% endblock %}
diff --git a/chloro/phyll/templates/phyll/note_form.html b/chloro/phyll/templates/phyll/note_form.html
new file mode 100644 (file)
index 0000000..3cc0ab5
--- /dev/null
@@ -0,0 +1,16 @@
+{% extends "phyll/base.html" %}
+{% load gadjo i18n static %}
+
+{% block bottom-head %}
+<script src="{% xstatic 'jquery' 'jquery.min.js' %}"></script>
+<script src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
+<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
+{% endblock %}
+
+{% block body %}
+<form method="post">
+{% csrf_token %}
+{{ form.as_p }}
+<button>{% trans "Submit" %}</button>
+</form>
+{% endblock %}
index e1ae6d1..03fa5cd 100644 (file)
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from django.conf.urls import url
+from django.contrib.admin.views.decorators import staff_member_required
+from django.views.decorators.cache import never_cache
+
+import ckeditor.views as ckeditor_views
 
 from . import views
 
 urlpatterns = [
+    url(
+        r'^ckeditor/upload/',
+        staff_member_required(ckeditor_views.upload, login_url='login'),
+        name='ckeditor_upload',
+    ),
+    url(
+        r'^ckeditor/browse/',
+        never_cache(staff_member_required(ckeditor_views.browse, login_url='login')),
+        name='ckeditor_browse',
+    ),
+    url(r'^(?P<slug>[\w:-]+)/edit/$', staff_member_required(views.NoteEditView.as_view(), login_url='login')),
+    url(
+        r'^(?P<slug>[\w:-]+)/delete/$',
+        staff_member_required(views.NoteDeleteView.as_view(), login_url='login'),
+    ),
+    url(r'^new-note/$', staff_member_required(views.NoteAddView.as_view(), login_url='login')),
     url(r'^(?P<slug>[\w:-]+)/$', views.NoteView.as_view()),
 ]
index cb12807..d13d6b9 100644 (file)
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from django.views.generic import DetailView
+from django.views.generic import CreateView, DeleteView, DetailView, UpdateView
 
 from .models import Note
 
 
 class NoteView(DetailView):
     model = Note
+
+
+class NoteEditView(UpdateView):
+    model = Note
+    fields = ['title', 'slug', 'text', 'tags']
+
+
+class NoteAddView(CreateView):
+    model = Note
+    fields = ['title', 'slug', 'text', 'tags']
+
+
+class NoteDeleteView(DeleteView):
+    model = Note
+
+    def get_success_url(self):
+        return '/'
index f3d06cd..dffc6df 100644 (file)
@@ -10,8 +10,11 @@ For the full list of settings and their values, see
 https://docs.djangoproject.com/en/1.11/ref/settings/
 """
 
+import copy
 import os
 
+from django.conf import global_settings
+
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
@@ -36,6 +39,8 @@ INSTALLED_APPS = [
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
+    'ckeditor',
+    'gadjo',
     'taggit',
     'chloro.phyll',
 ]
@@ -110,12 +115,34 @@ USE_TZ = True
 # https://docs.djangoproject.com/en/1.11/howto/static-files/
 STATIC_ROOT = os.path.join(BASE_DIR, 'collected-static')
 STATIC_URL = '/static/'
-
+STATICFILES_FINDERS = list(global_settings.STATICFILES_FINDERS) + ['gadjo.finders.XStaticFinder']
 
 # Media files
 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
 MEDIA_URL = '/media/'
 
+# ckeditor
+CKEDITOR_UPLOAD_PATH = 'uploads/'
+CKEDITOR_IMAGE_BACKEND = 'pillow'
+
+CKEDITOR_CONFIGS = {
+    'default': {
+        'allowedContent': True,
+        'removePlugins': 'stylesheetparser',
+        'toolbar_Own': [
+            ['Source', 'Format', '-', 'Bold', 'Italic'],
+            ['NumberedList', 'BulletedList'],
+            ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
+            ['Link', 'Unlink'],
+            ['Image', '-', 'HorizontalRule'],
+            ['RemoveFormat',],
+            ['Maximize'],
+        ],
+        'toolbar': 'Own',
+        'resize_enabled': False,
+        'height': 500,
+    },
+}
 
 local_settings_file = os.environ.get(
     'CHLORO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
index abcdbf4..20ef722 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -145,7 +145,7 @@ setup(
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
     ],
-    install_requires=['django>=1.11, <1.12', 'django-ckeditor<=4.5.3', 'django-taggit',],
+    install_requires=['django>=1.11, <1.12', 'django-ckeditor<=4.5.3', 'django-taggit', 'gadjo'],
     zip_safe=False,
     cmdclass={
         'build': build,