diff options
-rw-r--r-- | app/ccg_notes/__init__.py | 0 | ||||
-rw-r--r-- | app/ccg_notes/admin.py | 32 | ||||
-rw-r--r-- | app/ccg_notes/autocomplete_light_registry.py | 24 | ||||
-rw-r--r-- | app/ccg_notes/build.py | 36 | ||||
-rw-r--r-- | app/ccg_notes/forms.py | 15 | ||||
-rw-r--r-- | app/ccg_notes/mdx_urlize.py | 81 | ||||
-rw-r--r-- | app/ccg_notes/migrations/0001_initial.py | 33 | ||||
-rw-r--r-- | app/ccg_notes/migrations/__init__.py | 0 | ||||
-rw-r--r-- | app/ccg_notes/models.py | 36 | ||||
-rw-r--r-- | app/ccg_notes/urls.py | 62 | ||||
-rw-r--r-- | app/ccg_notes/views.py | 85 | ||||
-rw-r--r-- | design/templates/admin/ccg_notes/change_form.html | 152 |
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] + ' – <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> +› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a> +› {% if has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} +› {% 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 %} |