diff options
author | luxagraf <sng@luxagraf.net> | 2015-11-13 11:09:59 -0500 |
---|---|---|
committer | luxagraf <sng@luxagraf.net> | 2015-11-13 11:09:59 -0500 |
commit | 8d91fcde168b141ffa33173380e75739fdf45cd2 (patch) | |
tree | 8b9a4e493461df5bec0f60b846d0ce3e48d3f43c | |
parent | 92abffcc98404825324341d5ee654642364890bb (diff) |
rewrote Links to make them more browse/earacble on live site and in
admin, also ported to CBVs/new build system and added back some
templates for viewing. Deleted tumblr integration.
-rw-r--r-- | app/links/admin.py | 4 | ||||
-rw-r--r-- | app/links/detail_urls.py | 10 | ||||
-rw-r--r-- | app/links/models.py | 9 | ||||
-rw-r--r-- | app/links/retriever.py | 34 | ||||
-rw-r--r-- | app/links/tumblr.py | 240 | ||||
-rw-r--r-- | app/links/urls.py | 48 | ||||
-rw-r--r-- | app/links/views.py | 33 | ||||
-rw-r--r-- | app/utils/widgets.py | 38 | ||||
-rw-r--r-- | config/base_urls.py | 4 | ||||
-rw-r--r-- | design/sass/_inbox.scss | 11 | ||||
-rw-r--r-- | design/templates/archives/links.html | 23 | ||||
-rw-r--r-- | design/templates/details/link.html | 19 |
12 files changed, 174 insertions, 299 deletions
diff --git a/app/links/admin.py b/app/links/admin.py index f80e665..cbfbc52 100644 --- a/app/links/admin.py +++ b/app/links/admin.py @@ -1,11 +1,13 @@ from django.contrib import admin + +from utils.widgets import TagListFilter from links.models import Link class LinkAdmin(admin.ModelAdmin): list_display = ('title', 'admin_link', 'rating', 'pub_date', 'status') search_fields = ['title', 'description', 'url'] - list_filter = ['rating', 'status'] + list_filter = ['rating', 'status', TagListFilter] fieldsets = ( (None, { 'fields': ( diff --git a/app/links/detail_urls.py b/app/links/detail_urls.py deleted file mode 100644 index 9c76a7f..0000000 --- a/app/links/detail_urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.conf.urls import * -from django.views.generic.base import RedirectView -from django.views.generic.detail import DetailView -from links.models import Link - - -urlpatterns = patterns('', - (r'^$', RedirectView.as_view(url="/links/1/")), - (r'^(?P<object_id>\d+)/$', DetailView.as_view(model=Link, template_name='details/link.html')), -) diff --git a/app/links/models.py b/app/links/models.py index 11a6798..cdfd786 100644 --- a/app/links/models.py +++ b/app/links/models.py @@ -1,10 +1,13 @@ import datetime +from django.core.urlresolvers import reverse from django.utils.encoding import force_text from django.db import models from django.contrib.sitemaps import Sitemap from django.contrib.syndication.views import Feed from taggit.managers import TaggableManager +import markdown + RATINGS = ( ('1', "1 Star"), ('2', "2 Stars"), @@ -36,10 +39,10 @@ class Link(models.Model): return self.title def get_absolute_url(self): - return self.url + return reverse("links:detail", kwargs={"slug": self.pk}) - def get_model_name(self): - return 'link' + def render_description(self): + return markdown.markdown(self.description, extensions=['extra'], safe_mode=False) def get_previous_published(self): return self.get_previous_by_pub_date(status__exact=1) diff --git a/app/links/retriever.py b/app/links/retriever.py index 9aec34d..843487a 100644 --- a/app/links/retriever.py +++ b/app/links/retriever.py @@ -6,19 +6,18 @@ from django.core.mail import EmailMessage from django.conf import settings from links.models import Link -# https://github.com/mgan59/python-pinboard/ -#import pinboard import requests import json + def sync_pinboard_links(): PB_URL = "https://api.pinboard.in/v1/posts/all?results=70&format=json" r = requests.get(PB_URL, auth=((settings.PIN_USER, settings.PIN_PASS))) links = json.loads(r.text) for link in links: try: - #check to see if link exists + # check to see if link exists row = Link.objects.get(link_id=link['hash']) print("already have" + row.title) except ObjectDoesNotExist: @@ -37,35 +36,6 @@ def sync_pinboard_links(): l.tags.add(t) email_link(l) -""" -def sync_pinboard_links_old(): - sync bookmarks from my pinboard account - dependancies: python-pinboard https://github.com/mgan59/python-pinboard/ - p = pinboard.open(settings.PIN_USER, settings.PIN_PASS) - dupe = False - links = p.posts(count=30) - for link in links: - try: - #check to see if link exists - row = Link.objects.get(link_id=safestr(link['hash'])) - except ObjectDoesNotExist: - l, created = Link.objects.get_or_create( - title=link['description'], - link_id=safestr(link['hash']), - url=safestr(link['href']), - description=safestr(link['extended']), - rating="3", - pub_date=datetime.datetime.strptime(link['time'], "%Y-%m-%dT%H:%M:%SZ"), - status=0 - ) - print l.title - if created: - print l.title - for t in link['tags']: - l.tags.add(t) - email_link(l) - -""" def email_link(link): """ diff --git a/app/links/tumblr.py b/app/links/tumblr.py deleted file mode 100644 index 8c0e286..0000000 --- a/app/links/tumblr.py +++ /dev/null @@ -1,240 +0,0 @@ -#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright 2008 Ryan Cox ( ryan.a.cox@gmail.com ) All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-'''A wrapper library for Tumblr's public web API: http://www.tumblr.com/api'''
-
-__author__ = 'ryan.a.cox@gmail.com'
-__version__ = '0.1'
-
-
-from urllib2 import Request, urlopen, URLError, HTTPError
-from urllib import urlencode, quote
-import base64
-import re
-
-try:
- import simplejson
-except ImportError:
- from django.utils import simplejson
-
-GENERATOR = 'python-tumblr'
-PAGESIZE = 50
-
-
-class TumblrError(Exception):
- ''' General Tumblr error '''
- def __init__(self, msg):
- self.msg = msg
-
- def __str__(self):
- return self.msg
-
-class TumblrAuthError(TumblrError):
- ''' Wraps a 403 result '''
- pass
-
-class TumblrRequestError(TumblrError):
- ''' Wraps a 400 result '''
- pass
-
-class TumblrIterator:
- def __init__(self, name, start, max, type):
- self.name = name
- self.start = start
- self.max = max
- self.type = type
- self.results = None
- self.index = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if not self.results or (self.index == len(self.results['posts'])):
- self.start += self.index
- self.index = 0
- url = "http://%s.tumblr.com/api/read/json?start=%s&num=%s" % (self.name,self.start, PAGESIZE)
- if self.type:
- url += "&type=" + self.type
- response = urlopen(url)
- page = response.read()
- m = re.match("^.*?({.*}).*$", page,re.DOTALL | re.MULTILINE | re.UNICODE)
- self.results = simplejson.loads(m.group(1))
-
- if (self.index >= self.max) or len(self.results['posts']) == 0:
- raise StopIteration
-
- self.index += 1
- return self.results['posts'][self.index-1]
-
-class Api:
- def __init__(self, name, email=None, password=None ):
- self.name = name
- self.is_authenticated = False
- self.email = email
- self.password = password
-
- def auth_check(self):
- if self.is_authenticated:
- return
- url = 'http://www.tumblr.com/api/write'
- values = {
- 'action': 'authenticate',
- 'generator' : GENERATOR,
- 'email': self.email,
- 'password' : self.password }
-
- data = urlencode(values)
- req = Request(url, data)
- try:
- response = urlopen(req)
- page = response.read()
- self.url = page
- self.is_authenticated = True
- return
- except HTTPError, e:
- if 403 == e.code:
- raise TumblrAuthError(str(e))
- if 400 == e.code:
- raise TumblrRequestError(str(e))
- except Exception, e:
- raise TumblrError(str(e))
-
-
- def write_regular(self, title=None, body=None, **args):
- if title:
- args['title'] = title
- if body:
- args['body'] = body
- args = self._fixnames(args)
- if not 'title' in args and not 'body' in args:
- raise TumblrError("Must supply either body or title argument")
-
- self.auth_check()
- args['type'] = 'regular'
- return self._write(args)
-
- def write_photo(self, source=None, **args):
- if source:
- args['source'] = source
-
- args = self._fixnames(args)
- if 'source' in args and 'data' in args:
- raise TumblrError("Must NOT supply both source and data arguments")
-
- if not 'source' in args and not 'data' in args:
- raise TumblrError("Must supply source or data argument")
-
- self.auth_check()
- args['type'] = 'photo'
- return self._write(args)
-
- def write_quote(self, quote=None, **args):
- if quote:
- args['quote'] = quote
- args = self._fixnames(args)
- if not 'quote' in args:
- raise TumblrError("Must supply quote arguments")
-
- self.auth_check()
- args['type'] = 'quote'
- return self._write(args)
-
- def write_link(self, url=None, **args):
- if url:
- args['url'] = url
- args = self._fixnames(args)
- if not 'url' in args:
- raise TumblrError("Must supply url argument")
-
- self.auth_check()
- args['type'] = 'link'
- return self._write(args)
-
- def write_conversation(self, conversation=None, **args):
- if conversation:
- args['conversation'] = conversation
- args = self._fixnames(args)
- if not 'conversation' in args:
- raise TumblrError("Must supply conversation argument")
-
- self.auth_check()
- args['type'] = 'conversation'
- return self._write(args)
-
- def write_video(self, embed=None, **args):
- if embed:
- args['embed'] = embed
- args = self._fixnames(args)
- if 'embed' in args and 'data' in args:
- raise TumblrError("Must NOT supply both embed and data arguments")
-
- if not 'embed' in args and not 'data' in args:
- raise TumblrError("Must supply embed or data argument")
-
- self.auth_check()
- args['type'] = 'video'
- return self._write(args)
-
- def _fixnames(self, args):
- for key in args:
- if '_' in key:
- value = args[key]
- del args[key]
- args[key.replace('_', '-')] = value
- return args
-
- def _write(self, params, headers=None):
- self.auth_check()
- url = 'http://www.tumblr.com/api/write'
- params['email'] = self.email
- params['password'] = self.password
- params['generator'] = GENERATOR
- data = urlencode(params)
- if headers:
- req = Request(url, data, headers)
- else:
- req = Request(url, data)
- newid = None
- try:
- urlopen(req)
- raise TumblrError("Error writing post")
-
- except HTTPError, e:
- if 201 == e.code:
- newid = e.read()
- return self.read(id=newid)
- raise TumblrError(e.read())
-
- def read(self, id=None, start=0,max=2**31-1,type=None):
- if id:
- url = "http://%s.tumblr.com/api/read/json?id=%s" % (self.name,id)
- print url
- response = urlopen(url)
- page = response.read()
- m = re.match("^.*?({.*}).*$", page,re.DOTALL | re.MULTILINE | re.UNICODE)
- results = simplejson.loads(m.group(1))
- if len(results['posts']) == 0:
- return None
-
- return results['posts'][0]
- else:
- return TumblrIterator(self.name,start,max,type)
-
-if __name__ == "__main__":
- pass
diff --git a/app/links/urls.py b/app/links/urls.py index fa8e042..5fdaf30 100644 --- a/app/links/urls.py +++ b/app/links/urls.py @@ -1,15 +1,37 @@ -from django.conf.urls import * -from django.views.generic import list_detail -from django.views.generic.simple import redirect_to -from links.models import Link +from django.conf.urls import url +from django.views.generic.base import RedirectView -photos_paged = { - 'queryset': Link.objects.filter(status__exact=1).order_by('-pub_date'), - 'paginate': True, - 'page_url': '/links/%d/', -} +from . import views -urlpatterns = patterns('', - (r'^(?P<page>\d+)/$', list_detail.object_list, dict(photos_paged, template_name='archives/links.html')), - (r'^$', redirect_to, {'url': '/links/1/'}), -) +urlpatterns = [ + url( + regex=r'tag/(?P<slug>[-\w]+)/(?P<page>\d+)/$', + view=views.LinkTagListView.as_view(), + name='list-tag' + ), + url( + regex=r'(?P<slug>[-\d]+).txt$', + view=views.LinkDetailViewTXT.as_view(), + name="detail-txt" + ), + url( + regex=r'(?P<slug>[-\d]+)$', + view=views.LinkDetailView.as_view(), + name='detail' + ), + url( + regex=r'(?P<page>\d+)/$', + view=views.LinkListView.as_view(), + name='list' + ), + url( + regex=r'^tag/(?P<slug>[-\w]+)/$', + view=RedirectView.as_view(url="/links/tag/%(slug)s/1/", permanent=False), + name="live-redirect-tag" + ), + url( + regex=r'^$', + view=RedirectView.as_view(url="/links/1/", permanent=False), + name="live-redirect" + ), +] diff --git a/app/links/views.py b/app/links/views.py new file mode 100644 index 0000000..36ccca3 --- /dev/null +++ b/app/links/views.py @@ -0,0 +1,33 @@ +from django.views.generic.detail import DetailView +from utils.views import PaginatedListView + +from .models import Link +from taggit.models import Tag + + +class LinkListView(PaginatedListView): + model = Link + template_name = 'archives/links.html' + + +class LinkTagListView(PaginatedListView): + model = Link + template_name = 'archives/links.html' + + def get_queryset(self): + return Link.objects.filter(tags__name=self.kwargs['slug']) + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super(LinkTagListView, self).get_context_data(**kwargs) + context['tag'] = Tag.objects.get(slug__exact=self.kwargs['slug']) + return context + +class LinkDetailView(DetailView): + model = Link + template_name = "details/link.html" + slug_field = "id" + + +class LinkDetailViewTXT(LinkDetailView): + template_name = "details/entry.txt" diff --git a/app/utils/widgets.py b/app/utils/widgets.py index dc0c4e0..944601e 100644 --- a/app/utils/widgets.py +++ b/app/utils/widgets.py @@ -1,10 +1,48 @@ import os from django import forms +from django.contrib import admin from django.contrib.admin.widgets import AdminFileWidget from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ from django.conf import settings +class TagListFilter(admin.SimpleListFilter): + # Human-readable title which will be displayed in the + # right admin sidebar just above the filter options. + title = _('tag') + + # Parameter for the filter that will be used in the URL query. + parameter_name = 'tag' + + def lookups(self, request, model_admin): + """ + Returns a list of tuples. The first element in each + tuple is the coded value for the option that will + appear in the URL query. The second element is the + human-readable name for the option that will appear + in the right sidebar. + """ + tl = [] + self.model_to_use = model_admin.model + for t in self.model_to_use.tags.all().order_by('name'): + tl += (t.name, t.name), + return tl + + def queryset(self, request, queryset): + """ + Returns the filtered queryset based on the value + provided in the query string and retrievable via + `self.value()`. + """ + qs = self.model_to_use.objects.all() + try: + request.GET['tag'] + return qs.filter(tags__name=self.value()) + except: + return qs + + def thumbnail(image_path): absolute_url = os.path.join(settings.IMAGES_URL, image_path[7:]) print(absolute_url) diff --git a/config/base_urls.py b/config/base_urls.py index 9e2f628..99e9929 100644 --- a/config/base_urls.py +++ b/config/base_urls.py @@ -47,6 +47,10 @@ urlpatterns += patterns('', #(r'^contact/', TemplateView.as_view(template_name='details/contact.html')), (r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}), url( + regex=r'^links/', + view=include('links.urls', namespace='links') + ), + url( regex=r'^jrnl/', view=include('jrnl.urls', namespace='jrnl') ), diff --git a/design/sass/_inbox.scss b/design/sass/_inbox.scss index e69de29..d58c539 100644 --- a/design/sass/_inbox.scss +++ b/design/sass/_inbox.scss @@ -0,0 +1,11 @@ +.links main { + @include constrain_narrow; + .link-title a { + color: $body_font; + font-weight: 400; + text-decoration: none; + } + @include breakpoint(beta) { + text-align: left; + } +} diff --git a/design/templates/archives/links.html b/design/templates/archives/links.html new file mode 100644 index 0000000..646385c --- /dev/null +++ b/design/templates/archives/links.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} +{% load pagination_tags %} +{% block pagetitle %} Links | luxagraf {% endblock %} +{% block metadescription %} {% endblock %} +{%block bodyid%}class="links" id="links-archive"{%endblock%} + +{% block primary %} + <ul class="bl" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <li><a href="/" title="luxagraf homepage" itemprop="url"><span itemprop="title">Home</span></a> → </li> + {% if tag %}<li><a href="/links/" title="all links" itemprop="url"><span itemprop="title">Links</span></a> → </li><li>{{tag}}</li>{%else%}<li>Links</li>{% endif%} + </ul> + <main role="main">{% autopaginate object_list 100 %} {% for object in object_list %} + <article> + <h3 class="link-title"><a href="{{object.get_absolute_url}}">{{object.title|smartypants|safe}}</a> <span><a href="{{object.url}}">→</a></h3> + + </article> + {% endfor %} + </main> + <nav class="pagination"> + {% paginate %} + </nav> +{% endblock %} diff --git a/design/templates/details/link.html b/design/templates/details/link.html new file mode 100644 index 0000000..6ba0619 --- /dev/null +++ b/design/templates/details/link.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} +{%block bodyid%}class="links"{%endblock%} +{% block primary %}<ul class="bl" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <li><a href="/" title="luxagraf homepage" itemprop="url"><span itemprop="title">Home</span></a> → </li> + <li><a href="/links/" title="links" itemprop="url"><span itemprop="title">links</span></a> → </li> + </ul> + <main> + <h2 title="link-title">{{object.title|smartypants|widont|safe}}</h2> + <h3>{{object.url|urlize}}</h3> + <article class="h-entry hentry post--article book" itemscope itemType="http://schema.org/Article"> + {{object.render_description|amp|smartypants|safe}} + <ul>{% for tag in object.tags.all %} + <li><a href="/links/tag/{{tag.slug}}/">{{tag}}</a></li> + {%endfor%}</ul> + </article> +</main> +{% endblock %} + |