summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/ccg_notes/__init__.py0
-rw-r--r--app/ccg_notes/admin.py32
-rw-r--r--app/ccg_notes/autocomplete_light_registry.py24
-rw-r--r--app/ccg_notes/build.py36
-rw-r--r--app/ccg_notes/forms.py15
-rw-r--r--app/ccg_notes/mdx_urlize.py81
-rw-r--r--app/ccg_notes/migrations/0001_initial.py33
-rw-r--r--app/ccg_notes/migrations/__init__.py0
-rw-r--r--app/ccg_notes/models.py36
-rw-r--r--app/ccg_notes/urls.py62
-rw-r--r--app/ccg_notes/views.py85
-rw-r--r--design/templates/admin/ccg_notes/change_form.html152
12 files changed, 556 insertions, 0 deletions
diff --git a/app/ccg_notes/__init__.py b/app/ccg_notes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/ccg_notes/__init__.py
diff --git a/app/ccg_notes/admin.py b/app/ccg_notes/admin.py
new file mode 100644
index 0000000..5aec3ae
--- /dev/null
+++ b/app/ccg_notes/admin.py
@@ -0,0 +1,32 @@
+from django.contrib import admin
+
+from utils.widgets import OLAdminBase
+from utils.widgets import TagListFilter
+
+from .models import CcgNote
+from .forms import CcgNoteForm
+
+class CcgNoteAdmin(OLAdminBase):
+ form = CcgNoteForm
+ prepopulated_fields = {"slug": ('title',)}
+ list_display = ('slug', 'pub_date',)
+ list_filter = ['status', TagListFilter]
+ fieldsets = (
+ ('Note', {
+ 'fields': (
+ ('title', 'slug'),
+ 'body_markdown',
+ 'tags',
+ ('pub_date', 'status'),
+ ),
+ 'classes': (
+ 'show',
+ 'extrapretty',
+ 'wide'
+ )
+ }
+ ),
+ )
+
+
+admin.site.register(CcgNote, CcgNoteAdmin)
diff --git a/app/ccg_notes/autocomplete_light_registry.py b/app/ccg_notes/autocomplete_light_registry.py
new file mode 100644
index 0000000..0781848
--- /dev/null
+++ b/app/ccg_notes/autocomplete_light_registry.py
@@ -0,0 +1,24 @@
+import autocomplete_light.shortcuts as al
+from taggit.models import Tag
+
+# This will generate a PersonAutocomplete class
+al.register(Tag,
+ # Just like in ModelAdmin.search_fields
+ search_fields=['name'],
+ attrs={
+ # This will set the input placeholder attribute:
+ 'placeholder': 'Tags...',
+ # This will set the yourlabs.Autocomplete.minimumCharacters
+ # options, the naming conversion is handled by jQuery
+ 'data-autocomplete-minimum-characters': 1,
+},
+ # This will set the data-widget-maximum-values attribute on the
+ # widget container element, and will be set to
+ # yourlabs.Widget.maximumValues (jQuery handles the naming
+ # conversion).
+ widget_attrs={
+ 'data-widget-maximum-values': 4,
+ # Enable modern-style widget !
+ 'class': 'modern-style',
+ },
+)
diff --git a/app/ccg_notes/build.py b/app/ccg_notes/build.py
new file mode 100644
index 0000000..499adc6
--- /dev/null
+++ b/app/ccg_notes/build.py
@@ -0,0 +1,36 @@
+import os
+from django.core.urlresolvers import reverse
+from builder.base import BuildNew
+
+
+class BuildNotes(BuildNew):
+
+ def build(self):
+ self.build_detail_view()
+ self.build_list_view(
+ base_path=reverse("notes:live_redirect"),
+ paginate_by=24
+ )
+ self.build_year_view("notes:list_year")
+ self.build_month_view("notes:list_month")
+
+ def get_model_queryset(self):
+ return self.model.objects.all()
+
+ def build_detail_view(self):
+ '''
+ write out all the expenses for each trip
+ '''
+ for obj in self.get_model_queryset():
+ url = obj.get_absolute_url()
+ path, slug = os.path.split(url)
+ path = '%s/' % path
+ # write html
+ response = self.client.get(url)
+ print(path, slug)
+ self.write_file(path, response.content, filename=slug)
+
+
+def builder():
+ j = BuildNotes("notes", "luxnote")
+ j.build()
diff --git a/app/ccg_notes/forms.py b/app/ccg_notes/forms.py
new file mode 100644
index 0000000..5de83bd
--- /dev/null
+++ b/app/ccg_notes/forms.py
@@ -0,0 +1,15 @@
+from django import forms
+import autocomplete_light
+from autocomplete_light.contrib.taggit_field import TaggitField, TaggitWidget
+from .models import CcgNote
+
+
+class CcgNoteForm(forms.ModelForm):
+ tags = TaggitField(widget=TaggitWidget('TagAutocomplete'))
+
+ class Meta:
+ widgets = {
+ 'body_markdown': forms.Textarea(attrs={'rows': 18, 'cols': 60}),
+ }
+ fields = '__all__'
+ model = CcgNote
diff --git a/app/ccg_notes/mdx_urlize.py b/app/ccg_notes/mdx_urlize.py
new file mode 100644
index 0000000..dc8d1d7
--- /dev/null
+++ b/app/ccg_notes/mdx_urlize.py
@@ -0,0 +1,81 @@
+"""A more liberal autolinker
+
+Inspired by Django's urlize function.
+
+Positive examples:
+
+>>> import markdown
+>>> md = markdown.Markdown(extensions=['urlize'])
+
+>>> md.convert('http://example.com/')
+u'<p><a href="http://example.com/">http://example.com/</a></p>'
+
+>>> md.convert('go to http://example.com')
+u'<p>go to <a href="http://example.com">http://example.com</a></p>'
+
+>>> md.convert('example.com')
+u'<p><a href="http://example.com">example.com</a></p>'
+
+>>> md.convert('example.net')
+u'<p><a href="http://example.net">example.net</a></p>'
+
+>>> md.convert('www.example.us')
+u'<p><a href="http://www.example.us">www.example.us</a></p>'
+
+>>> md.convert('(www.example.us/path/?name=val)')
+u'<p>(<a href="http://www.example.us/path/?name=val">www.example.us/path/?name=val</a>)</p>'
+
+>>> md.convert('go to <http://example.com> now!')
+u'<p>go to <a href="http://example.com">http://example.com</a> now!</p>'
+
+Negative examples:
+
+>>> md.convert('del.icio.us')
+u'<p>del.icio.us</p>'
+
+"""
+
+import markdown
+
+# Global Vars
+URLIZE_RE = '(%s)' % '|'.join([
+ r'<(?:f|ht)tps?://[^>]*>',
+ r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]',
+ r'\bwww\.[^)<>\s]+[^.,)<>\s]',
+ r'[^(<\s]+\.(?:com|net|org)\b',
+])
+
+class UrlizePattern(markdown.inlinepatterns.Pattern):
+ """ Return a link Element given an autolink (`http://example/com`). """
+ def handleMatch(self, m):
+ url = m.group(2)
+
+ if url.startswith('<'):
+ url = url[1:-1]
+
+ text = url
+
+ if not url.split('://')[0] in ('http','https','ftp'):
+ if '@' in url and not '/' in url:
+ url = 'mailto:' + url
+ else:
+ url = 'http://' + url
+
+ el = markdown.util.etree.Element("a")
+ el.set('href', url)
+ el.text = markdown.util.AtomicString(text)
+ return el
+
+class UrlizeExtension(markdown.Extension):
+ """ Urlize Extension for Python-Markdown. """
+
+ def extendMarkdown(self, md, md_globals):
+ """ Replace autolink with UrlizePattern """
+ md.inlinePatterns['autolink'] = UrlizePattern(URLIZE_RE, md)
+
+def makeExtension(configs=None):
+ return UrlizeExtension(configs=configs)
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/app/ccg_notes/migrations/0001_initial.py b/app/ccg_notes/migrations/0001_initial.py
new file mode 100644
index 0000000..1877e91
--- /dev/null
+++ b/app/ccg_notes/migrations/0001_initial.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2016-07-22 19:54
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+import taggit.managers
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('taggit', '0002_auto_20150616_2121'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CcgNote',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(blank=True, max_length=250, null=True)),
+ ('slug', models.SlugField(blank=True, unique_for_date='pub_date')),
+ ('pub_date', models.DateTimeField(default=django.utils.timezone.now)),
+ ('date_last_updated', models.DateTimeField(blank=True, verbose_name='Date')),
+ ('body_html', models.TextField(blank=True)),
+ ('body_markdown', models.TextField(verbose_name='Note')),
+ ('status', models.IntegerField(choices=[(0, 'Draft'), (1, 'Published')], default=1)),
+ ('tags', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
+ ],
+ ),
+ ]
diff --git a/app/ccg_notes/migrations/__init__.py b/app/ccg_notes/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/ccg_notes/migrations/__init__.py
diff --git a/app/ccg_notes/models.py b/app/ccg_notes/models.py
new file mode 100644
index 0000000..b235d36
--- /dev/null
+++ b/app/ccg_notes/models.py
@@ -0,0 +1,36 @@
+from django.contrib.gis.db import models
+from django.utils import timezone
+from django.core.urlresolvers import reverse
+
+from taggit.managers import TaggableManager
+from utils.widgets import markdown_to_html
+from jrnl.models import render_images
+
+
+class CcgNote(models.Model):
+ title = models.CharField(max_length=250, null=True, blank=True)
+ slug = models.SlugField(unique_for_date='pub_date', blank=True)
+ pub_date = models.DateTimeField(default=timezone.now)
+ date_last_updated = models.DateTimeField('Date', blank=True)
+ body_html = models.TextField(blank=True)
+ body_markdown = models.TextField('Note')
+ PUB_STATUS = (
+ (0, 'Draft'),
+ (1, 'Published'),
+ )
+ status = models.IntegerField(choices=PUB_STATUS, default=1)
+ tags = TaggableManager(blank=True)
+
+ def __str__(self):
+ return self.title
+
+ def get_absolute_url(self):
+ return reverse("ccg_notes:detail", kwargs={"year": self.pub_date.year, "month": self.pub_date.strftime("%m"), "slug": self.slug})
+
+ def save(self, *args, **kwargs):
+ md = render_images(self.body_markdown)
+ self.body_html = markdown_to_html(md)
+ if not self.id:
+ self.pub_date = timezone.now()
+ self.date_last_updated = timezone.now()
+ super(CcgNote, self).save()
diff --git a/app/ccg_notes/urls.py b/app/ccg_notes/urls.py
new file mode 100644
index 0000000..0f9fad7
--- /dev/null
+++ b/app/ccg_notes/urls.py
@@ -0,0 +1,62 @@
+from django.conf.urls import url
+from django.views.generic.base import RedirectView
+
+from . import views
+
+app_name = "notes"
+
+urlpatterns = [
+ url(
+ r'(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+).txt$',
+ views.NoteDetailViewTXT.as_view(),
+ name="detail-txt"
+ ),
+ url(
+ r'(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+).amp$',
+ views.NoteDetailViewAMP.as_view(),
+ name="detail-amp"
+ ),
+ url(
+ r'(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)$',
+ views.NoteDetailView.as_view(),
+ name="detail"
+ ),
+ url(
+ r'^(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',
+ views.NoteMonthArchiveView.as_view(month_format='%m'),
+ name="list_month"
+ ),
+ url(
+ r'(?P<year>\d{4})/$',
+ views.NoteYearArchiveView.as_view(),
+ name="list_year"
+ ),
+
+
+ url(
+ r'(?P<year>\d{4})/(?P<month>\d{2})/$',
+ views.date_list,
+ name="notes_by_month"
+ ),
+ url(
+ r'(?P<year>\d{4})/$',
+ views.date_list,
+ name="notes_by_year"
+ ),
+ url(
+ r'(?P<page>\d+)/$',
+ views.NoteList.as_view(),
+ name="list"
+ ),
+ # redirect / to /1/ for live server
+ url(
+ r'',
+ RedirectView.as_view(url="/field-notes/1/", permanent=False),
+ name="live_redirect"
+ ),
+ url(
+ r'^$',
+ views.entry_list,
+ name="notes_archive"
+ ),
+]
diff --git a/app/ccg_notes/views.py b/app/ccg_notes/views.py
new file mode 100644
index 0000000..1fbe6f4
--- /dev/null
+++ b/app/ccg_notes/views.py
@@ -0,0 +1,85 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+from django.views.generic.dates import YearArchiveView, MonthArchiveView
+from django.views.generic.detail import DetailView
+
+from utils.views import PaginatedListView
+
+from notes.models import LuxNote, Note
+
+
+class NoteList(PaginatedListView):
+ """
+ Return a list of Notes in reverse chronological order
+ """
+ queryset = LuxNote.objects.all().order_by('-pub_date')
+ template_name = "archives/notes.html"
+
+
+class NoteDetailView(DetailView):
+ model = LuxNote
+ template_name = "details/note.html"
+ slug_field = "slug"
+
+
+class NoteDetailViewTXT(NoteDetailView):
+ template_name = "details/entry.txt"
+
+
+class NoteDetailViewAMP(NoteDetailView):
+ template_name = "details/entry.amp"
+
+
+class NoteYearArchiveView(YearArchiveView):
+ queryset = LuxNote.objects.all()
+ date_field = "pub_date"
+ make_object_list = True
+ allow_future = True
+ template_name = "archives/notes_date.html"
+
+
+class NoteMonthArchiveView(MonthArchiveView):
+ queryset = LuxNote.objects.all()
+ date_field = "pub_date"
+ allow_future = True
+ template_name = "archives/notes_date.html"
+
+
+"""
+Legacy Notes views
+"""
+
+
+def entry_detail(request, year, month, slug):
+ context = {
+ 'object': get_object_or_404(Note, slug__exact=slug),
+ }
+ return render_to_response(
+ 'details/note.html',
+ context,
+ context_instance=RequestContext(request)
+ )
+
+
+def date_list(request, year, month=None):
+ if month:
+ qs = Note.objects.filter(date_created__year=year, date_created__month=month).order_by('-date_created')
+ else:
+ qs = Note.objects.filter(date_created__year=year).order_by('-date_created')
+ context = {
+ 'year': year,
+ 'month': month,
+ 'object_list': qs,
+ }
+ return render_to_response(
+ "archives/notes_date.html",
+ context,
+ context_instance=RequestContext(request)
+ )
+
+
+def entry_list(request):
+ context = {
+ 'object_list': Note.objects.all().order_by('-date_created').select_related(),
+ }
+ return render_to_response("archives/notes.html", context, context_instance=RequestContext(request))
diff --git a/design/templates/admin/ccg_notes/change_form.html b/design/templates/admin/ccg_notes/change_form.html
new file mode 100644
index 0000000..a240f90
--- /dev/null
+++ b/design/templates/admin/ccg_notes/change_form.html
@@ -0,0 +1,152 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_urls admin_static admin_modify %}
+
+{% block extrahead %}{{ block.super }}
+<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
+{{ media }}
+<script>
+if (!$) {
+ $ = django.jQuery;
+}
+$(function(){
+ $('#id_body_markdown').each(function(){
+ $(this).after('<iframe frameborder="0" style="border: #dddddd 1px solid;margin-left: 20px;width:330px; height:340px;" src="/luximages/insert/?textarea='+this.id+'"></iframe>');
+ });
+ $('#id_images').css('width', '500px').css('height', '400px');
+ $('#id_images option').each(function(){
+ $(this).attr('style', 'background: url('+$(this).text().split("qq")[1]+') no-repeat; background-size: 120px 80px; height: 80px; padding-left: 125px; line-height: 80px; margin-bottom: 4px; padding-bottom: 5px;border-bottom: #eee 1px solid;');
+ $(this).html($(this).text().split("qq")[0] + ' &ndash; <a href="/admin/photos/luximage/'+ $(this).text().split("qq")[2]+'/change/">edit</a>')
+ });
+});
+</script>
+
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
+
+{% block coltype %}colM{% endblock %}
+
+{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
+
+{% if not is_popup %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
+&rsaquo; {% if has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
+&rsaquo; {% if add %}{% trans 'Add' %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
+</div>
+{% endblock %}
+{% endif %}
+
+{% block content %}
+ <a class="btn" onclick="geoFindMe();" href="javascript:void(0);" class="historylink">Get Location</a>
+<div id="content-main">
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+ <ul class="object-tools">
+ {% block object-tools-items %}
+
+ <li>
+ <a onclick="geoFindMe();" href="javascript:void(0);" class="historylink">Get Location</a>
+ </li>
+ <li>
+ <a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a>
+ </li>
+ {% if has_absolute_url %}
+ <li>
+ <a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a>
+ </li>
+ {% endif %}
+ <li>
+ {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
+ <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
+ </li>
+ {% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif %}
+ {% endblock %}
+ </ul>
+{% endif %}{% endif %}
+{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
+<div>
+{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1" />{% endif %}
+{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}" />{% endif %}
+{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
+{% if errors %}
+ <p class="errornote">
+ {% if errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
+ </p>
+ {{ adminform.form.non_field_errors }}
+{% endif %}
+
+{% block field_sets %}
+{% for fieldset in adminform %}
+ {% include "admin/includes/fieldset.html" %}
+{% endfor %}
+{% endblock %}
+
+{% block after_field_sets %}{% endblock %}
+
+{% block inline_field_sets %}
+{% for inline_admin_formset in inline_admin_formsets %}
+ {% include inline_admin_formset.opts.template %}
+{% endfor %}
+{% endblock %}
+
+{% block after_related_objects %}{% endblock %}
+
+{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
+
+{% block admin_change_form_document_ready %}
+ <script type="text/javascript">
+ (function($) {
+ $(document).ready(function() {
+ $('.add-another').click(function(e) {
+ e.preventDefault();
+ var event = $.Event('django:add-another-related');
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ showAddAnotherPopup(this);
+ }
+ });
+ $('.related-lookup').click(function(e) {
+ e.preventDefault();
+ var event = $.Event('django:lookup-related');
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ showRelatedObjectLookupPopup(this);
+ }
+ });
+ $('body').on('click', '.related-widget-wrapper-link', function(e) {
+ e.preventDefault();
+ if (this.href) {
+ var event = $.Event('django:show-related', {href: this.href});
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ showRelatedObjectPopup(this);
+ }
+ }
+ });
+ $('body').on('change', '.related-widget-wrapper select', function(e) {
+ var event = $.Event('django:update-related');
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ updateRelatedObjectLinks(this);
+ }
+ });
+ $('.related-widget-wrapper select').trigger('change');
+
+ {% if adminform and add %}
+ $('form#{{ opts.model_name }}_form :input:visible:enabled:first').focus()
+ {% endif %}
+ });
+ })(django.jQuery);
+ </script>
+{% endblock %}
+
+{# JavaScript for prepopulated fields #}
+{% prepopulated_fields_js %}
+
+</div>
+</form></div>
+{% endblock %}