From afa00c4e717e265dc868f58fadd03bb9dbcdeaaf Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Wed, 17 Jun 2015 15:41:24 -0400 Subject: [PATCH 01/34] initial commit --- migrations/__init__.py | 0 setup.cfg | 13 ++ setup.py | 8 + sheerlike/__init__.py | 42 +++++ sheerlike/admin.py | 3 + sheerlike/filters.py | 103 ++++++++++++ sheerlike/middleware.py | 11 ++ sheerlike/models.py | 3 + sheerlike/query.py | 311 ++++++++++++++++++++++++++++++++++++ sheerlike/templates.py | 11 ++ sheerlike/tests.py | 3 + sheerlike/views/__init__.py | 3 + 12 files changed, 511 insertions(+) create mode 100644 migrations/__init__.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 sheerlike/__init__.py create mode 100644 sheerlike/admin.py create mode 100644 sheerlike/filters.py create mode 100644 sheerlike/middleware.py create mode 100644 sheerlike/models.py create mode 100644 sheerlike/query.py create mode 100644 sheerlike/templates.py create mode 100644 sheerlike/tests.py create mode 100644 sheerlike/views/__init__.py diff --git a/migrations/__init__.py b/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9c01aac --- /dev/null +++ b/setup.cfg @@ -0,0 +1,13 @@ +[metadata] +name = django-sheerlike +author = CFPB +author-email = tech@cfpb.gov +summary = OpenStack's setup automation in a reusable form +description-file = README.md +license = Public Domain (CC0) +keywords = + setup + distutils +[files] +packages = + sheerlike diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..aa2d8a0 --- /dev/null +++ b/setup.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup( + setup_requires=['pbr'], + pbr=True, +) diff --git a/sheerlike/__init__.py b/sheerlike/__init__.py new file mode 100644 index 0000000..cf2b2d0 --- /dev/null +++ b/sheerlike/__init__.py @@ -0,0 +1,42 @@ +from __future__ import absolute_import # Python 2 only + +import functools + +from django.contrib.staticfiles.storage import staticfiles_storage +from django.core.urlresolvers import reverse + +from jinja2 import Environment + +from .query import QueryFinder, more_like_this, get_document +from .filters import selected_filters_for_field, is_filter_selected +from .templates import date_formatter +from .middleware import get_request + +def url_for(app, filename): + if app == 'static': + return staticfiles_storage.url(filename) + else: + raise ValueError("url_for doesn't know about %s" % app) + +def date_filter(value, format="%Y-%m-%d"): + return date_formatter(value, format) + + +def environment(**options): + queryfinder = QueryFinder() + + env = Environment(**options) + env.globals.update({ + 'static': staticfiles_storage.url, + 'url_for':url_for, + 'url': reverse, + 'queries': queryfinder, + 'more_like_this': more_like_this, + 'get_document': get_document, + 'selected_filters_for_field': selected_filters_for_field, + 'is_filter_selected': is_filter_selected, + }) + env.filters.update({ + 'date':date_filter + }) + return env diff --git a/sheerlike/admin.py b/sheerlike/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/sheerlike/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/sheerlike/filters.py b/sheerlike/filters.py new file mode 100644 index 0000000..604a7e0 --- /dev/null +++ b/sheerlike/filters.py @@ -0,0 +1,103 @@ +import re +import calendar +import datetime +from dateutil.parser import parse + +from .middleware import get_request + +def generate_term_filters(multidict, filter_keys): + ''' + This generates the ElasticSearch filter DSL for filters that check whether + a field matches a certain string. Groupings of the same exact filter, such + as filter_fieldname, are combined into an OR filter, while the major + groupings of separate filters are all combined into an AND filter. + ''' + term_main = {"and": []} + for key in filter_keys: + field = key.replace('filter_', '') + filter_type_main = {"or": []} + values = multidict.getlist(key) + for val in values: + term_single = {"term": {}} + term_single["term"][field] = val + filter_type_main["or"].append(term_single) + term_main["and"].append(filter_type_main) + return term_main + +def generate_range_filters(multidict, filter_keys): + ''' + This generates the ElasticSearch filter DSL for filters that check whether + a field is within a certain range + ''' + range_clause = {"range": {}} + for key in filter_keys: + full_field = key.replace('filter_range_', '') + # We account for potential underscores in the field name itself + # e.g. comment_count + operator = full_field[full_field.rfind('_') + 1:] + field = full_field[:full_field.rfind('_')] + if field not in range_clause["range"]: + range_clause["range"][field] = {} + # If there are multiples of the same date filter, this will take + # the first + value = multidict.getlist(key)[0] + range_clause["range"][field][operator] = value + + # Validate date range input + + # First check if both date_lte and date_gte are present + # If the 'start' date is after the 'end' date, swap them + if 'date' in range_clause['range']: + if all(x in range_clause['range']['date'] for x in ('lte', 'gte')) and \ + parse(range_clause['range']['date']['gte'], default=datetime.date.today().replace(day=1)) > \ + parse(range_clause['range']['date']['lte'], default=datetime.date.today().replace(day=1)): + range_clause['range']['date']['gte'], range_clause['range']['date']['lte'] = \ + range_clause['range']['date'][ + 'lte'], range_clause['range']['date']['gte'] + # If either date matches the YYYY-M[M] format, append the + # appropriate day + if 'lte' in range_clause['range']['date'] and \ + re.compile("^[0-9]{4}-[0-9]{1,2}$").match(range_clause['range']['date']['lte']): + year, month = range_clause['range']['date']['lte'].split('-') + last_day_of_month = calendar.monthrange( + int(year), int(month))[1] + range_clause['range']['date'][ + 'lte'] += "-{0}".format(last_day_of_month) + if 'gte' in range_clause['range']['date'] and \ + re.compile("^[0-9]{4}-[0-9]{1,2}$").match(range_clause['range']['date']['gte']): + range_clause['range']['date']['gte'] += "-1" + return range_clause + + + +def filter_dsl_from_multidict(multidict): + # Split the filters between 'range' and 'term', making sure the query + # value isn't blank + term_filter_keys = [r for r in [k for k in multidict.keys() if re.compile("^filter_(?!range_)").match(k)] + if multidict[r]] + range_filter_keys = [r for r in [k for k in multidict.keys() if re.compile("^filter_range_").match(k)] + if multidict[r]] + final_filters = [] + if term_filter_keys: + term_clause = generate_term_filters(multidict, term_filter_keys) + final_filters.append(term_clause) + + if range_filter_keys: + range_clause = generate_range_filters(multidict, range_filter_keys) + final_filters.append(range_clause) + return final_filters + + +def selected_filters_from_multidict(multidict, field): + return [k for k in multidict.getlist('filter_' + field) if k] + + +def selected_filters_for_field(fieldname): + multidict = get_request().GET + return selected_filters_from_multidict(multidict, fieldname) + + +def is_filter_selected(fieldname, value): + multidict = get_request().GET + return value in selected_filters_from_multidict(multidict, fieldname) + diff --git a/sheerlike/middleware.py b/sheerlike/middleware.py new file mode 100644 index 0000000..4a914b1 --- /dev/null +++ b/sheerlike/middleware.py @@ -0,0 +1,11 @@ +from threading import local + +_active = local() + +def get_request(): + return _active.request + +class GlobalRequestMiddleware(object): + def process_view(self, request, view_func, view_args, view_kwargs): + _active.request = request + return None diff --git a/sheerlike/models.py b/sheerlike/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/sheerlike/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/sheerlike/query.py b/sheerlike/query.py new file mode 100644 index 0000000..1a993dd --- /dev/null +++ b/sheerlike/query.py @@ -0,0 +1,311 @@ +import os +import codecs +import logging +import json + +from collections import namedtuple + +import django.utils.datastructures +from django.conf import settings +from django.utils.http import urlencode + +import dateutil.parser + +from time import mktime, strptime +import datetime + +import elasticsearch + +#from sheer.utility import find_in_search_path +from .filters import filter_dsl_from_multidict + +from .middleware import get_request + + +ALLOWED_SEARCH_PARAMS = ('doc_type', + 'analyze_wildcard', 'analyzer', 'default_operator', 'df', + 'explain', 'fields', 'indices_boost', 'lenient', + 'allow_no_indices', 'expand_wildcards', 'ignore_unavailable', + 'lowercase_expanded_terms', 'from_', 'preference', 'q', 'routing', + 'scroll', 'search_type', 'size', 'sort', 'source', 'stats', + 'suggest_field', 'suggest_mode', 'suggest_size', 'suggest_text', 'timeout', + 'version') + + +FakeQuery = namedtuple('FakeQuery',['es','es_index']) + + + +def mapping_for_type(typename, es, es_index): + + return es.indices.get_mapping(index=es_index, doc_type=typename) + + +def field_or_source_value(fieldname, hit_dict): + if 'fields' in hit_dict and fieldname in hit_dict['fields']: + return hit_dict['fields'][fieldname] + + if '_source' in hit_dict and fieldname in hit_dict['_source']: + return hit_dict['_source'][fieldname] + + +def datatype_for_fieldname_in_mapping(fieldname, hit_type, mapping_dict, es, es_index): + + try: + return mapping_dict[es_index]["mappings"][hit_type]["properties"][fieldname]["type"] + except KeyError: + return None + + +def coerced_value(value, datatype): + if datatype == None or value == None: + return value + + TYPE_MAP = {'string': unicode, + 'date': dateutil.parser.parse, + 'dict': dict, + 'float': float, + 'long': float, + 'boolean': bool} + + coercer = TYPE_MAP[datatype] + + if type(value) == list: + if value and type(value[0]) == list: + return [[coercer(y) for y in v] for v in value] + else: + return [coercer(v) for v in value] or "" + else: + return coercer(value) + + +class QueryHit(object): + + def __init__(self, hit_dict, es, es_index): + self.hit_dict = hit_dict + self.type = hit_dict['_type'] + self.es = es + self.es_index = es_index + self.mapping = mapping_for_type(self.type, es=es, es_index=es_index) + + def __str__(self): + return str(self.hit_dict.get('_source')) + + def __repr__(self): + return self.__str__() + + @property + def permalink(self): + raise NotImplementedError("Please use django's reverse url system") + + def __getattr__(self, attrname): + value = field_or_source_value(attrname, self.hit_dict) + datatype = datatype_for_fieldname_in_mapping( + attrname, self.type, self.mapping, self.es, self.es_index) + return coerced_value(value, datatype) + + def json_compatible(self): + hit_dict = self.hit_dict + fields = hit_dict.get('fields') or hit_dict.get('_source', {}).keys() + return dict((field, getattr(self, field)) for field in fields) + + +class QueryResults(object): + + def __init__(self, query, result_dict, pagenum=1): + self.result_dict = result_dict + self.total = int(result_dict['hits']['total']) + self.query = query + + # confusing: using the word 'query' to mean different things + # above, it's the Query object + # below, it's Elasticsearch query DSL + + if 'query' in result_dict: + self.size = int(result_dict['query'].get('size', '10')) + self.from_ = int(result_dict['query'].get('from', 1)) + self.pages = self.total / self.size + \ + int(self.total % self.size > 0) + else: + self.size, self.from_, self.pages = 10, 1, 1 + + self.current_page = pagenum + + def __iter__(self): + if 'hits' in self.result_dict and 'hits' in self.result_dict['hits']: + for hit in self.result_dict['hits']['hits']: + query_hit = QueryHit(hit, self.query.es, self.query.es_index) + yield query_hit + + def aggregations(self, fieldname): + if "aggregations" in self.result_dict and \ + fieldname in self.result_dict['aggregations']: + return self.result_dict['aggregations'][fieldname]['buckets'] + + def json_compatible(self): + response_data = {} + response_data['total'] = self.result_dict['hits']['total'] + if self.size: + response_data['size'] = self.size + + if self.from_: + response_data['from'] = self.from_ + + if self.pages: + response_data['pages'] = self.pages + response_data['results'] = [ + hit.json_compatible() for hit in self.__iter__()] + return response_data + + def url_for_page(self, pagenum): + request = get_request() + current_args = request.GET + args_dict = django.utils.datastructures.MultiValueDict(current_args) + if pagenum != 1: + args_dict['page'] = pagenum + elif 'page' in args_dict: + del args_dict['page'] + + encoded = urlencode(args_dict, doseq=True) + if encoded: + url = "".join([request.path, "?", urlencode(args_dict, doseq=True)]) + return url + else: + return request.path + + +class Query(object): + + def __init__(self, filename,es, es_index, json_safe=False): + # TODO: make the no filename case work + + self.es_index = es_index + self.es = es + self.filename = filename + self.__results = None + self.json_safe = json_safe + + def search_with_url_arguments(self, aggregations=None, **kwargs): + query_file = json.loads(file(self.filename).read()) + query_dict = query_file['query'] + + ''' + These dict constructors split the kwargs from the template into filter + arguments and arguments that can be placed directly into the query body. + The dict constructor syntax supports python 2.6, 2.7, and 3.x + If python 2.7, use dict comprehension and iteritems() + With python 3, use dict comprehension and items() (items() replaces + iteritems and is just as fast) + ''' + filter_args = dict((key, value) for (key, value) in kwargs.items() + if key.startswith('filter_')) + non_filter_args = dict((key, value) for (key, value) in kwargs.items() + if not key.startswith('filter_')) + query_dict.update(non_filter_args) + pagenum = 1 + + request = get_request() + # Add in filters from the template. + new_multidict = request.GET.copy() + for key, value in filter_args.items(): + new_multidict.add(key, value) + url_filters = filter_dsl_from_multidict(new_multidict) + args_flat = request.GET.copy() + query_body = {} + + if aggregations: + aggs_dsl = {} + if type(aggregations) is str: + aggregations = [aggregations] # so we can treat it as a list + for fieldname in aggregations: + aggs_dsl[fieldname] = {'terms': + {'field': fieldname, 'size': 10000}} + query_body['aggs'] = aggs_dsl + else: + if 'page' in args_flat: + args_flat['from_'] = int( + query_dict.get('size', '10')) * (int(args_flat['page']) - 1) + pagenum = int(args_flat['page']) + + args_flat_filtered = dict( + [(k, v) for k, v in args_flat.items() if v]) + query_dict.update(args_flat_filtered) + query_body['query'] = {'filtered': {'filter': {}}} + if url_filters: + query_body['query']['filtered']['filter'][ + 'and'] = [f for f in url_filters] + + if 'filters' in query_file: + if 'and' not in query_body['query']['filtered']['filter']: + query_body['query']['filtered']['filter']['and'] = [] + for json_filter in query_file['filters']: + query_body['query']['filtered'][ + 'filter']['and'].append(json_filter) + final_query_dict = dict((k, v) + for (k, v) in query_dict.items() if k in ALLOWED_SEARCH_PARAMS) + final_query_dict['index'] = self.es_index + final_query_dict['body'] = query_body + response = self.es.search(**final_query_dict) + response['query'] = query_dict + return QueryResults(self,response, pagenum) + + def possible_values_for(self, field, **kwargs): + results = self.search_with_url_arguments(aggregations=[field], **kwargs) + return results.aggregations(field) + + def search(self): + query_file = json.loads(file(self.filename).read()) + query_dict = query_file['query'] + + response = self.es.search(index=es_index, body=query_dict) + + return QueryResults(self,response) + + + +class QueryFinder(object): + + def __init__(self): + self.es = elasticsearch.Elasticsearch(settings.SHEER_ELASTICSEARCH_SERVER) + self.es_index = settings.SHEER_ELASTICSEARCH_INDEX + + def __getattr__(self, name): + for dir in settings.SHEER_QUERIES_DIRS: + query_filename = name + ".json" + query_file_path = os.path.join(dir, query_filename) + + if os.path.exists(query_file_path): + query = Query(query_file_path, self.es, self.es_index) + return query + + +class QueryJsonEncoder(json.JSONEncoder): + query_classes = [QueryResults, QueryHit] + + def default(self, obj): + if type(obj) in (datetime.datetime, datetime.date): + return obj.isoformat() + if type(obj) in self.query_classes: + return obj.json_compatible() + + return json.JSONEncoder.default(self, obj) + + +def more_like_this(hit, **kwargs): + es = elasticsearch.Elasticsearch(settings.SHEER_ELASTICSEARCH_SERVER) + es_index = settings.SHEER_ELASTICSEARCH_INDEX + doctype, docid = hit.type, hit._id + raw_results = es.mlt( + index=es_index, doc_type=doctype, id=docid, **kwargs) + + # this is bad and I should feel bad + # (I do) + fake_query = FakeQuery(es,es_index) + return QueryResults(fake_query,raw_results) + +def get_document(doctype, docid): + es = elasticsearch.Elasticsearch(settings.SHEER_ELASTICSEARCH_SERVER) + es_index = settings.SHEER_ELASTICSEARCH_INDEX + raw_results = es.get(index=es_index, doc_type=doctype, id=docid) + return QueryHit(raw_results, es, es_index) + diff --git a/sheerlike/templates.py b/sheerlike/templates.py new file mode 100644 index 0000000..dbd87c6 --- /dev/null +++ b/sheerlike/templates.py @@ -0,0 +1,11 @@ +import datetime +from dateutil import parser + + +def date_formatter(value, format="%Y-%m-%d"): + if type(value) not in [datetime.datetime, datetime.date]: + dt = parser.parse(value, default=datetime.date.today().replace(day=1)) + else: + dt = value + + return dt.strftime(format) diff --git a/sheerlike/tests.py b/sheerlike/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/sheerlike/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/sheerlike/views/__init__.py b/sheerlike/views/__init__.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/sheerlike/views/__init__.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 1cfb4f4dca046877371d3f8c4e37dbc35cea1ff2 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Fri, 19 Jun 2015 11:41:10 -0400 Subject: [PATCH 02/34] - restores .permalink (using a registry mechanism) - provides a generic view which should suffice for most uses of sheer's "lookups" feature. --- sheerlike/__init__.py | 7 ++++++- sheerlike/query.py | 9 ++++++++- sheerlike/views/generic.py | 23 +++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 sheerlike/views/generic.py diff --git a/sheerlike/__init__.py b/sheerlike/__init__.py index cf2b2d0..5ba5908 100644 --- a/sheerlike/__init__.py +++ b/sheerlike/__init__.py @@ -5,13 +5,18 @@ from django.contrib.staticfiles.storage import staticfiles_storage from django.core.urlresolvers import reverse -from jinja2 import Environment +from jinja2 import Environment, StrictUndefined from .query import QueryFinder, more_like_this, get_document from .filters import selected_filters_for_field, is_filter_selected from .templates import date_formatter from .middleware import get_request +PERMALINK_REGISTRY={} + +def register_permalink(sheer_type, url_pattern_name): + PERMALINK_REGISTRY[sheer_type]=url_pattern_name + def url_for(app, filename): if app == 'static': return staticfiles_storage.url(filename) diff --git a/sheerlike/query.py b/sheerlike/query.py index 1a993dd..5a23c60 100644 --- a/sheerlike/query.py +++ b/sheerlike/query.py @@ -8,6 +8,7 @@ import django.utils.datastructures from django.conf import settings from django.utils.http import urlencode +from django.core.urlresolvers import reverse import dateutil.parser @@ -96,7 +97,13 @@ def __repr__(self): @property def permalink(self): - raise NotImplementedError("Please use django's reverse url system") + import sheerlike + if self.type in sheerlike.PERMALINK_REGISTRY: + pattern_name = sheerlike.PERMALINK_REGISTRY[self.type] + return reverse(pattern_name,kwargs=dict(doc_id=self._id)) + else: + raise NotImplementedError("Please use django's reverse url system," + "or register a permalink for %s" % self.type) def __getattr__(self, attrname): value = field_or_source_value(attrname, self.hit_dict) diff --git a/sheerlike/views/generic.py b/sheerlike/views/generic.py new file mode 100644 index 0000000..5157a4e --- /dev/null +++ b/sheerlike/views/generic.py @@ -0,0 +1,23 @@ +from django.views.generic.base import TemplateView +from django.http import Http404 + +from elasticsearch import TransportError + +from sheerlike.query import get_document + +class SheerDetailView(TemplateView): + doc_type = None + local_name = 'object' + + def get_context_data(self, **kwargs): + doc_id = kwargs.pop('doc_id') + context = super(SheerDetailView, self).get_context_data(**self.kwargs) + try: + document = get_document(doctype=self.doc_type, + docid=doc_id) + context[self.local_name] = document + + return context + except TransportError: + raise Http404("Document does not exist") + From f00bec22708069ba6c0ab71e908a1f20fe86d20d Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Fri, 19 Jun 2015 11:45:33 -0400 Subject: [PATCH 03/34] Guidance re: .permalink is no longer accurate --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index 8a1d0e9..f86149e 100644 --- a/README.md +++ b/README.md @@ -30,19 +30,6 @@ Almost all of the rest of the sheer machinery is still intact, though: you still ## Template tweaks -Replace references to 'some_result.permalink' to use [Django's URL reversing system](https://docs.djangoproject.com/en/1.8/ref/urlresolvers/#django.core.urlresolvers.reverse). - -this: -``` - -``` - -becomes: -``` - -``` Eliminate relative template includes/imports. for example, in (cfgov-refresh) blog/index.html: From 85f4564c52eb9662a5998ed9785a6ebbe1de2032 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Fri, 19 Jun 2015 12:13:06 -0400 Subject: [PATCH 04/34] Formatting fixes, and added information on SheerDetailView --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f86149e..febb82d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # django-sheerlike -This is an attempt to port some of our favorite [sheer](https://github.com/cfpb/sheer) features over to Django. +This is an attempt to port some of our favorite +[sheer](https://github.com/cfpb/sheer) features over to Django. **Current status**: Not usable for any purpose. @@ -8,42 +9,90 @@ This is an attempt to port some of our favorite [sheer](https://github.com/cfpb/ # Philosophy -It's our goal to respect the work that people have put into building sites for Sheer, but also avoid coloring too far outside the lines of how Django works. +It's our goal to respect the work that people have put into building sites for +Sheer, but also avoid coloring too far outside the lines of how Django works. # Required changes -The biggest change is that the bundle of files that we were calling a "sheer site", is now best thought of as a set of templates for apps that should be defined in the proper Django form. [cfgov-refresh](https://github.com/cfpb/cfgov-refresh) describes many "apps" (blog, newsroom, activity feed, etc), while [Owning a Home](https://github.com/cfpb/owning-a-home/) probably only describes one. +The biggest change is that the bundle of files that we were calling a "sheer +site", is now best thought of as a set of templates for apps that should be +defined in the proper Django form. +[cfgov-refresh](https://github.com/cfpb/cfgov-refresh) describes many "apps" +(blog, newsroom, activity feed, etc), while [Owning a +Home](https://github.com/cfpb/owning-a-home/) probably only describes one. As stated in [Two Scoops of Django 1.8](http://twoscoopspress.org/products/two-scoops-of-django-1-8): -_"each app should be tightly focused on its task. If an app can’t be explained in a single sentence of moderate length, or you need to say ‘and’ more than once, it probably means the app is too big and should be broken up."_ +_"each app should be tightly focused on its task. If an app can’t be explained +in a single sentence of moderate length, or you need to say ‘and’ more than +once, it probably means the app is too big and should be broken up."_ -Sheer's URL routing goes away entirely. If a particular URL renders a particular template, it's because it was specified in a Django view. If a template presumes the existence of a "post" item on blog post detail, that object will have to be created in the Django view, and passed into the template context. This is pretty simple, though: +Sheer's URL routing goes away entirely. If a particular URL renders a +particular template, it's because it was specified in a Django view. If a +template presumes the existence of a "post" item on blog post detail, that +object will have to be created in the Django view, and passed into the template +context. We've provided a generic view that makes this pretty simple: ```python -def blog_detail(request, slug): - post = get_document(doctype='posts', docid=slug) - return render(request, 'blog/_single.html', context={'post':post) + url(r'^blog/(?P[\w-]+)/$', SheerDetailView.as_view( + doc_type='posts', + local_name='post', + template_name='blog/_single.html', + ), name='blog_detail'), ``` -Almost all of the rest of the sheer machinery is still intact, though: you still have access to the global 'queries' object, and can still call functions like get_document and more_like_this. +Almost all of the rest of the sheer machinery is still intact, though: you +still have access to the global 'queries' object, and can still call functions +like get_document and more_like_this. ## Template tweaks - -Eliminate relative template includes/imports. for example, in (cfgov-refresh) blog/index.html: +Eliminate relative template includes/imports. for example, in (cfgov-refresh) +blog/index.html: `{% import "_vars-blog.html" as vars with context %}` becomes `{% import "blog/_vars-blog.html" as vars with context %}` -The request object is a context variable now, so in order to reference it in 'imported' templates, [you must specify 'with context'](http://jinja.pocoo.org/docs/dev/templates/#import-context-behavior). +The request object is a context variable now, so in order to reference it in +'imported' templates, [you must specify 'with +context'](http://jinja.pocoo.org/docs/dev/templates/#import-context-behavior). + +For example, `{% from "macros.html" import share as share %}` becomes `{% from +"macros.html" import share as share with context%}` + +Also, the [Django request +object](https://docs.djangoproject.com/en/1.8/ref/request-response/#httprequest-objects) +has different properties and methods than the one available in Flask/sheer. -For example, `{% from "macros.html" import share as share %}` becomes `{% from "macros.html" import share as share with context%}` +Inline IF statements MUST have an else clause, [otherwise the output is +undefined](http://jinja.pocoo.org/docs/dev/templates/#if-expression) -Also, the [Django request object](https://docs.djangoproject.com/en/1.8/ref/request-response/#httprequest-objects) has different properties and methods than the one available in Flask/sheer. +Old: -We'll probably find a few more things, so this list will grow. +``` +{% macro format_phone(number) %} + {%- for char in number -%} + {{- '(' if loop.index == 1 -}} + {{ char }} + {{- ') ' if loop.index == 3 -}} + {{- '-' if loop.index == 6 -}} + {%- endfor %} +{% endmacro %} +``` + + +New: +``` +{% macro format_phone(number) %} + {%- for char in number -%} + {{- '(' if loop.index == 1 else '' -}} + {{ char }} + {{- ') ' if loop.index == 3 else '' -}} + {{- '-' if loop.index == 6 else '' -}} + {%- endfor %} +{% endmacro %} +``` ## API's and RSS Feeds From 08fe46869a42e8e8d934125db8ed4bf9b5ef2958 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 22 Jun 2015 23:37:20 -0400 Subject: [PATCH 05/34] Filtering doesn't work yet, but now it fails differently --- sheerlike/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sheerlike/query.py b/sheerlike/query.py index 5a23c60..4abbeb2 100644 --- a/sheerlike/query.py +++ b/sheerlike/query.py @@ -215,7 +215,7 @@ def search_with_url_arguments(self, aggregations=None, **kwargs): # Add in filters from the template. new_multidict = request.GET.copy() for key, value in filter_args.items(): - new_multidict.add(key, value) + new_multidict.update({key: value}) url_filters = filter_dsl_from_multidict(new_multidict) args_flat = request.GET.copy() query_body = {} From 8fd915f8db6f0cc570eb174253d96399328e7c66 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 22 Jun 2015 23:47:02 -0400 Subject: [PATCH 06/34] This is madness! This is Python! --- README.md | 111 ------------------------------------------------- README.rst | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 111 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/README.md b/README.md deleted file mode 100644 index febb82d..0000000 --- a/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# django-sheerlike - -This is an attempt to port some of our favorite -[sheer](https://github.com/cfpb/sheer) features over to Django. - -**Current status**: Not usable for any purpose. - -**Runs on**: Django 1.8 and Python 2.7 - -# Philosophy - -It's our goal to respect the work that people have put into building sites for -Sheer, but also avoid coloring too far outside the lines of how Django works. - -# Required changes - -The biggest change is that the bundle of files that we were calling a "sheer -site", is now best thought of as a set of templates for apps that should be -defined in the proper Django form. -[cfgov-refresh](https://github.com/cfpb/cfgov-refresh) describes many "apps" -(blog, newsroom, activity feed, etc), while [Owning a -Home](https://github.com/cfpb/owning-a-home/) probably only describes one. - -As stated in [Two Scoops of Django 1.8](http://twoscoopspress.org/products/two-scoops-of-django-1-8): - -_"each app should be tightly focused on its task. If an app can’t be explained -in a single sentence of moderate length, or you need to say ‘and’ more than -once, it probably means the app is too big and should be broken up."_ - -Sheer's URL routing goes away entirely. If a particular URL renders a -particular template, it's because it was specified in a Django view. If a -template presumes the existence of a "post" item on blog post detail, that -object will have to be created in the Django view, and passed into the template -context. We've provided a generic view that makes this pretty simple: - -```python - url(r'^blog/(?P[\w-]+)/$', SheerDetailView.as_view( - doc_type='posts', - local_name='post', - template_name='blog/_single.html', - ), name='blog_detail'), -``` - -Almost all of the rest of the sheer machinery is still intact, though: you -still have access to the global 'queries' object, and can still call functions -like get_document and more_like_this. - -## Template tweaks - -Eliminate relative template includes/imports. for example, in (cfgov-refresh) -blog/index.html: - -`{% import "_vars-blog.html" as vars with context %}` - -becomes `{% import "blog/_vars-blog.html" as vars with context %}` - -The request object is a context variable now, so in order to reference it in -'imported' templates, [you must specify 'with -context'](http://jinja.pocoo.org/docs/dev/templates/#import-context-behavior). - -For example, `{% from "macros.html" import share as share %}` becomes `{% from -"macros.html" import share as share with context%}` - -Also, the [Django request -object](https://docs.djangoproject.com/en/1.8/ref/request-response/#httprequest-objects) -has different properties and methods than the one available in Flask/sheer. - -Inline IF statements MUST have an else clause, [otherwise the output is -undefined](http://jinja.pocoo.org/docs/dev/templates/#if-expression) - -Old: - -``` -{% macro format_phone(number) %} - {%- for char in number -%} - {{- '(' if loop.index == 1 -}} - {{ char }} - {{- ') ' if loop.index == 3 -}} - {{- '-' if loop.index == 6 -}} - {%- endfor %} -{% endmacro %} -``` - - -New: -``` -{% macro format_phone(number) %} - {%- for char in number -%} - {{- '(' if loop.index == 1 else '' -}} - {{ char }} - {{- ') ' if loop.index == 3 else '' -}} - {{- '-' if loop.index == 6 else '' -}} - {%- endfor %} -{% endmacro %} -``` - -## API's and RSS Feeds - -We will need to switch to native Django tools for such things. - -# Recommendations - -- Look for opportunities to replace complicated template logic with python views -- Switch to [Django Pagination](https://docs.djangoproject.com/en/1.8/topics/pagination/) - ----- - -## Open source licensing info -1. [TERMS](TERMS.md) -2. [LICENSE](LICENSE) -3. [CFPB Source Code Policy](https://github.com/cfpb/source-code-policy/) diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..1a1da0a --- /dev/null +++ b/README.rst @@ -0,0 +1,119 @@ +django-sheerlike +================ + +This is an attempt to port some of our favorite +`sheer `__ features over to Django. + +**Current status**: Not usable for any purpose. + +**Runs on**: Django 1.8 and Python 2.7 + +Philosophy +========== + +It's our goal to respect the work that people have put into building +sites for Sheer, but also avoid coloring too far outside the lines of +how Django works. + +Required changes +================ + +The biggest change is that the bundle of files that we were calling a +"sheer site", is now best thought of as a set of templates for apps that +should be defined in the proper Django form. +`cfgov-refresh `__ describes many +"apps" (blog, newsroom, activity feed, etc), while `Owning a +Home `__ probably only describes +one. + +As stated in `Two Scoops of Django +1.8 `__: + +*"each app should be tightly focused on its task. If an app can’t be +explained in a single sentence of moderate length, or you need to say +‘and’ more than once, it probably means the app is too big and should be +broken up."* + +Sheer's URL routing goes away entirely. If a particular URL renders a +particular template, it's because it was specified in a Django view. If +a template presumes the existence of a "post" item on blog post detail, +that object will have to be created in the Django view, and passed into +the template context. We've provided a generic view that makes this +pretty simple: + +.. code:: python + + url(r'^blog/(?P[\w-]+)/$', SheerDetailView.as_view( + doc_type='posts', + local_name='post', + template_name='blog/_single.html', + ), name='blog_detail'), + +Almost all of the rest of the sheer machinery is still intact, though: +you still have access to the global 'queries' object, and can still call +functions like get\_document and more\_like\_this. + +Template tweaks +--------------- + +Eliminate relative template includes/imports. for example, in +(cfgov-refresh) blog/index.html: + +``{% import "_vars-blog.html" as vars with context %}`` + +becomes ``{% import "blog/_vars-blog.html" as vars with context %}`` + +The request object is a context variable now, so in order to reference +it in 'imported' templates, `you must specify 'with +context' `__. + +For example, ``{% from "macros.html" import share as share %}`` becomes +``{% from "macros.html" import share as share with context%}`` + +Also, the `Django request +object `__ +has different properties and methods than the one available in +Flask/sheer. + +Inline IF statements MUST have an else clause, `otherwise the output is +undefined `__ + +Old: + +:: + + {% macro format_phone(number) %} + {%- for char in number -%} + {{- '(' if loop.index == 1 -}} + {{ char }} + {{- ') ' if loop.index == 3 -}} + {{- '-' if loop.index == 6 -}} + {%- endfor %} + {% endmacro %} + +New: +``{% macro format_phone(number) %} {%- for char in number -%} {{- '(' if loop.index == 1 else '' -}} {{ char }} {{- ') ' if loop.index == 3 else '' -}} {{- '-' if loop.index == 6 else '' -}} {%- endfor %} {% endmacro %}`` + +API's and RSS Feeds +------------------- + +We will need to switch to native Django tools for such things. + +Recommendations +=============== + +- Look for opportunities to replace complicated template logic with + python views +- Switch to `Django + Pagination `__ + +-------------- + +Open source licensing info +-------------------------- + +1. `TERMS `__ +2. LICENSE +3. `CFPB Source Code + Policy `__ + From 133933a3500182402e1bca46da87102d9d837fe4 Mon Sep 17 00:00:00 2001 From: Ross M Karchner Date: Mon, 22 Jun 2015 23:49:01 -0400 Subject: [PATCH 07/34] Update README.rst Now I should probably go learn ReStructuredText --- README.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1a1da0a..0abef36 100644 --- a/README.rst +++ b/README.rst @@ -92,7 +92,18 @@ Old: {% endmacro %} New: -``{% macro format_phone(number) %} {%- for char in number -%} {{- '(' if loop.index == 1 else '' -}} {{ char }} {{- ') ' if loop.index == 3 else '' -}} {{- '-' if loop.index == 6 else '' -}} {%- endfor %} {% endmacro %}`` + +:: + + {% macro format_phone(number) %} + {%- for char in number -%} + {{- '(' if loop.index == 1 else '' -}} + {{ char }} + {{- ') ' if loop.index == 3 else '' -}} + {{- '-' if loop.index == 6 else '' -}} + {%- endfor %} + {% endmacro %} +` API's and RSS Feeds ------------------- From af5754a258da8706474268ca0063c5255a88d325 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 22 Jun 2015 23:55:30 -0400 Subject: [PATCH 08/34] product of running sphinx-quickstart --- .gitignore | 3 +- Makefile | 192 +++++++++++++++++++++++++++++++++++ conf.py | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.rst | 22 ++++ make.bat | 263 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 771 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 conf.py create mode 100644 index.rst create mode 100644 make.bat diff --git a/.gitignore b/.gitignore index a8ba298..89e446b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.o *.so _site/ +_build/ # Packages # ############ @@ -20,7 +21,6 @@ _site/ *.rar *.tar *.zip - # Logs and databases # ###################### *.log @@ -48,6 +48,7 @@ Thumbs.db __pycache__/ *.py[cod] .env +.eggs/ # Django # ################# diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..33d2b8f --- /dev/null +++ b/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-sheerlike.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-sheerlike.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/django-sheerlike" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-sheerlike" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/conf.py b/conf.py new file mode 100644 index 0000000..b2672ff --- /dev/null +++ b/conf.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- +# +# django-sheerlike documentation build configuration file, created by +# sphinx-quickstart on Mon Jun 22 23:52:55 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-sheerlike' +copyright = u'2015, CFPB' +author = u'CFPB' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-sheerlikedoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'django-sheerlike.tex', u'django-sheerlike Documentation', + u'CFPB', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'django-sheerlike', u'django-sheerlike Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'django-sheerlike', u'django-sheerlike Documentation', + author, 'django-sheerlike', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/index.rst b/index.rst new file mode 100644 index 0000000..fd05f82 --- /dev/null +++ b/index.rst @@ -0,0 +1,22 @@ +.. django-sheerlike documentation master file, created by + sphinx-quickstart on Mon Jun 22 23:52:55 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to django-sheerlike's documentation! +============================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..ebd3de2 --- /dev/null +++ b/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-sheerlike.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-sheerlike.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end From d8a70d8f7cdda182a57786c2974104180c3b6206 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 23 Jun 2015 00:01:14 -0400 Subject: [PATCH 09/34] moved doc stub into docs folder --- INSTALL.md | 3 --- INSTALL.rst | 5 +++++ Makefile => docs/Makefile | 0 conf.py => docs/conf.py | 0 index.rst => docs/index.rst | 0 make.bat => docs/make.bat | 0 6 files changed, 5 insertions(+), 3 deletions(-) delete mode 100644 INSTALL.md create mode 100644 INSTALL.rst rename Makefile => docs/Makefile (100%) rename conf.py => docs/conf.py (100%) rename index.rst => docs/index.rst (100%) rename make.bat => docs/make.bat (100%) diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 4c199e6..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,3 +0,0 @@ -# Installation instructions - -Detailed instructions on how to install, configure, and get the project running. diff --git a/INSTALL.rst b/INSTALL.rst new file mode 100644 index 0000000..b2df9bd --- /dev/null +++ b/INSTALL.rst @@ -0,0 +1,5 @@ +Installation instructions +========================= + +Detailed instructions on how to install, configure, and get the project +running. diff --git a/Makefile b/docs/Makefile similarity index 100% rename from Makefile rename to docs/Makefile diff --git a/conf.py b/docs/conf.py similarity index 100% rename from conf.py rename to docs/conf.py diff --git a/index.rst b/docs/index.rst similarity index 100% rename from index.rst rename to docs/index.rst diff --git a/make.bat b/docs/make.bat similarity index 100% rename from make.bat rename to docs/make.bat From 0403fbb7ebdf1c6d6f6623ee873d9e118046c444 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 23 Jun 2015 00:08:04 -0400 Subject: [PATCH 10/34] trying to get autodoc working --- docs/Makefile | 1 - docs/index.rst | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 33d2b8f..6616593 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,4 @@ # Makefile for Sphinx documentation -# # You can set these variables from the command line. SPHINXOPTS = diff --git a/docs/index.rst b/docs/index.rst index fd05f82..c8007d8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,7 @@ Contents: .. toctree:: :maxdepth: 2 - +.. automodule:: sheerlike Indices and tables ================== From e1757029284287edfb323f02da524c5b73208b19 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 23 Jun 2015 11:35:55 -0400 Subject: [PATCH 11/34] remove unneeded import, and fix reference to README in setup.cfg --- setup.cfg | 2 +- sheerlike/__init__.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 9c01aac..3dbe540 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = django-sheerlike author = CFPB author-email = tech@cfpb.gov summary = OpenStack's setup automation in a reusable form -description-file = README.md +description-file = README.rst license = Public Domain (CC0) keywords = setup diff --git a/sheerlike/__init__.py b/sheerlike/__init__.py index 5ba5908..9d7a83b 100644 --- a/sheerlike/__init__.py +++ b/sheerlike/__init__.py @@ -5,7 +5,7 @@ from django.contrib.staticfiles.storage import staticfiles_storage from django.core.urlresolvers import reverse -from jinja2 import Environment, StrictUndefined +from jinja2 import Environment from .query import QueryFinder, more_like_this, get_document from .filters import selected_filters_for_field, is_filter_selected @@ -30,6 +30,9 @@ def date_filter(value, format="%Y-%m-%d"): def environment(**options): queryfinder = QueryFinder() + # Django defaults to DebugUndefined + # options['undefined'] = make_logging_undefined() + env = Environment(**options) env.globals.update({ 'static': staticfiles_storage.url, From e68ee505e3b18d122bd6598648dde942219c2905 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 23 Jun 2015 11:37:45 -0400 Subject: [PATCH 12/34] add elasticsearch depenedency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a0e2760 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +elasticsearch==1.6.0 From ade97a5d5a01b2de456978747fffb17f942888d0 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 23 Jun 2015 11:48:41 -0400 Subject: [PATCH 13/34] added jinja2 to the requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a0e2760..a037f83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ elasticsearch==1.6.0 +Jinja2==2.7.3 From 7aeac46bbf8ebee823dfabebbcca5a1349e2b403 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 10:01:31 -0400 Subject: [PATCH 14/34] Up to date with @kwall's latest changes to sheer.query https://github.com/cfpb/sheer/commit/4575ef0a8993cbb007ec7e187157a227ad4bb616 --- sheerlike/middleware.py | 17 +++++++++++++++++ sheerlike/query.py | 32 +++++++++++++++----------------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/sheerlike/middleware.py b/sheerlike/middleware.py index 4a914b1..057bf4a 100644 --- a/sheerlike/middleware.py +++ b/sheerlike/middleware.py @@ -1,11 +1,28 @@ from threading import local +from django.http import HttpResponse _active = local() def get_request(): return _active.request +class FlaskyHeaderGetter(object): + def __init__(self, request): + self.request= request + + def __getitem__(self, key): + django_key = 'HTTP_' + key.upper().replace('-','_') + return self.request.META.get(django_key) + + def get(self, key): + return self.__getitem__(key) + + + class GlobalRequestMiddleware(object): def process_view(self, request, view_func, view_args, view_kwargs): _active.request = request + request.headers = FlaskyHeaderGetter(request) + request.url = "%s://%s%s" % (request.scheme, request.get_host(), + request.get_full_path()) return None diff --git a/sheerlike/query.py b/sheerlike/query.py index 4abbeb2..b95e227 100644 --- a/sheerlike/query.py +++ b/sheerlike/query.py @@ -5,7 +5,7 @@ from collections import namedtuple -import django.utils.datastructures +from django.utils.datastructures import MultiValueDict as MultiDict from django.conf import settings from django.utils.http import urlencode from django.core.urlresolvers import reverse @@ -167,7 +167,7 @@ def json_compatible(self): def url_for_page(self, pagenum): request = get_request() current_args = request.GET - args_dict = django.utils.datastructures.MultiValueDict(current_args) + args_dict = MultiDict(current_args) if pagenum != 1: args_dict['page'] = pagenum elif 'page' in args_dict: @@ -192,7 +192,7 @@ def __init__(self, filename,es, es_index, json_safe=False): self.__results = None self.json_safe = json_safe - def search_with_url_arguments(self, aggregations=None, **kwargs): + def search(self, aggregations=None, use_url_arguments=True, size=10, **kwargs): query_file = json.loads(file(self.filename).read()) query_dict = query_file['query'] @@ -213,10 +213,16 @@ def search_with_url_arguments(self, aggregations=None, **kwargs): request = get_request() # Add in filters from the template. - new_multidict = request.GET.copy() + new_multidict = MultiDict() + # First add the url arguments if requested + if use_url_arguments: + new_multidict = request.GET.copy() + # Next add the arguments from the search() function used in the + # template for key, value in filter_args.items(): - new_multidict.update({key: value}) - url_filters = filter_dsl_from_multidict(new_multidict) + new_multidict.add(key, value) + + filters = filter_dsl_from_multidict(new_multidict) args_flat = request.GET.copy() query_body = {} @@ -238,9 +244,9 @@ def search_with_url_arguments(self, aggregations=None, **kwargs): [(k, v) for k, v in args_flat.items() if v]) query_dict.update(args_flat_filtered) query_body['query'] = {'filtered': {'filter': {}}} - if url_filters: + if filters: query_body['query']['filtered']['filter'][ - 'and'] = [f for f in url_filters] + 'and'] = [f for f in filters] if 'filters' in query_file: if 'and' not in query_body['query']['filtered']['filter']: @@ -257,17 +263,9 @@ def search_with_url_arguments(self, aggregations=None, **kwargs): return QueryResults(self,response, pagenum) def possible_values_for(self, field, **kwargs): - results = self.search_with_url_arguments(aggregations=[field], **kwargs) + results = self.search(aggregations=[field], **kwargs) return results.aggregations(field) - def search(self): - query_file = json.loads(file(self.filename).read()) - query_dict = query_file['query'] - - response = self.es.search(index=es_index, body=query_dict) - - return QueryResults(self,response) - class QueryFinder(object): From 9aa0ec46ebf773f68329ec988b189e1116011bc6 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 10:41:43 -0400 Subject: [PATCH 15/34] request.GET.copy() doesn't get you a mutable object. MultiValueDict(request.get.copy()) Does --- sheerlike/query.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sheerlike/query.py b/sheerlike/query.py index b95e227..60d8f66 100644 --- a/sheerlike/query.py +++ b/sheerlike/query.py @@ -216,11 +216,11 @@ def search(self, aggregations=None, use_url_arguments=True, size=10, **kwargs): new_multidict = MultiDict() # First add the url arguments if requested if use_url_arguments: - new_multidict = request.GET.copy() + new_multidict = MultiDict(request.GET.copy()) # Next add the arguments from the search() function used in the # template for key, value in filter_args.items(): - new_multidict.add(key, value) + new_multidict.update({key: value}) filters = filter_dsl_from_multidict(new_multidict) args_flat = request.GET.copy() From 1ceb9553547dbdb75c2c97fedbfbe2d5a9502dc7 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 16:01:27 -0400 Subject: [PATCH 16/34] Migrated some unit tests from Sheer, and configs for tox and Travis --- .travis.yml | 9 +++ TERMS.md | 52 ------------- requirements.txt | 1 + runtests.py | 23 ++++++ sheerlike/filters.py | 6 +- sheerlike/tests.py | 3 - test-requirements.txt | 2 + test_project/manage.py | 10 +++ test_project/test_project/__init__.py | 0 test_project/test_project/settings.py | 108 ++++++++++++++++++++++++++ test_project/test_project/urls.py | 21 +++++ test_project/test_project/wsgi.py | 16 ++++ tox.ini | 6 ++ 13 files changed, 201 insertions(+), 56 deletions(-) create mode 100644 .travis.yml delete mode 100644 TERMS.md create mode 100644 runtests.py delete mode 100644 sheerlike/tests.py create mode 100644 test-requirements.txt create mode 100755 test_project/manage.py create mode 100644 test_project/test_project/__init__.py create mode 100644 test_project/test_project/settings.py create mode 100644 test_project/test_project/urls.py create mode 100644 test_project/test_project/wsgi.py create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..06905eb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: python +install: + - pip install tox +script: + - tox +env: + - TOXENV=py27 + - TOXENV=py33 + - TOXENV=py34 diff --git a/TERMS.md b/TERMS.md deleted file mode 100644 index f64c133..0000000 --- a/TERMS.md +++ /dev/null @@ -1,52 +0,0 @@ -As a work of the United States Government, this package (excluding any -exceptions listed below) is in the public domain within the United States. -Additionally, we waive copyright and related rights in the work worldwide -through the [CC0 1.0 Universal public domain dedication][CC0]. - -Software source code previously released under an open source license and then -modified by CFPB staff or its contractors is considered a "joint work" -(see 17 USC § 101); it is partially copyrighted, partially public domain, -and as a whole is protected by the copyrights of the non-government authors and -must be released according to the terms of the original open-source license. -Segments written by CFPB staff, and by contractors who are developing software -on behalf of CFPB are also in the public domain, and copyright and related -rights for that work are waived through the CC0 1.0 Universal dedication. - -For further details, please see the CFPB [Source Code Policy][policy]. - - -## CC0 1.0 Universal Summary - -This is a human-readable summary of the [Legal Code (read the full text)][CC0]. - -### No Copyright - -The person who associated a work with this deed has dedicated the work to -the public domain by waiving all of his or her rights to the work worldwide -under copyright law, including all related and neighboring rights, to the -extent allowed by law. - -You can copy, modify, distribute and perform the work, even for commercial -purposes, all without asking permission. See Other Information below. - -### Other Information - -In no way are the patent or trademark rights of any person affected by CC0, -nor are the rights that other persons may have in the work or in how the -work is used, such as publicity or privacy rights. - -Unless expressly stated otherwise, the person who associated a work with -this deed makes no warranties about the work, and disclaims liability for -all uses of the work, to the fullest extent permitted by applicable law. -When using or citing the work, you should not imply endorsement by the -author or the affirmer. - -[policy]: https://github.com/cfpb/source-code-policy/ -[CC0]: http://creativecommons.org/publicdomain/zero/1.0/legalcode - - -## Exceptions - -_Source code or other assets that are excluded from the TERMS should be listed -here. These may include dependencies that may be licensed differently or are -not in the public domain._ diff --git a/requirements.txt b/requirements.txt index a037f83..73cec4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ elasticsearch==1.6.0 Jinja2==2.7.3 +python-dateutil==2.4.2 diff --git a/runtests.py b/runtests.py new file mode 100644 index 0000000..55ffa11 --- /dev/null +++ b/runtests.py @@ -0,0 +1,23 @@ +import os +import os.path +import sys + +import django +from django.core.management import call_command + +def run(): + this_dir = os.getcwd() + testproj_dir = os.path.join(this_dir, "test_project") + os.chdir(testproj_dir) + sys.path.append(testproj_dir) + os.environ["DJANGO_SETTINGS_MODULE"] = os.environ.get( + "DJANGO_SETTINGS_MODULE", "test_project.settings") + settings_file = os.environ["DJANGO_SETTINGS_MODULE"] + django.setup() + os.chdir(os.path.join(this_dir, 'sheerlike')) + call_command('test') + os.chdir(this_dir) + + +if __name__ == '__main__': + run() diff --git a/sheerlike/filters.py b/sheerlike/filters.py index 604a7e0..70003e2 100644 --- a/sheerlike/filters.py +++ b/sheerlike/filters.py @@ -17,6 +17,7 @@ def generate_term_filters(multidict, filter_keys): field = key.replace('filter_', '') filter_type_main = {"or": []} values = multidict.getlist(key) + #from nose.tools import set_trace; set_trace(); for val in values: term_single = {"term": {}} term_single["term"][field] = val @@ -40,7 +41,10 @@ def generate_range_filters(multidict, filter_keys): range_clause["range"][field] = {} # If there are multiples of the same date filter, this will take # the first - value = multidict.getlist(key)[0] + + # The django version of MultiDict returns the actual object + # if there is only one, and a list otherwise + value = multidict.get(key) range_clause["range"][field][operator] = value # Validate date range input diff --git a/sheerlike/tests.py b/sheerlike/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/sheerlike/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..5f2ea3d --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +-r requirements.txt +django-nose diff --git a/test_project/manage.py b/test_project/manage.py new file mode 100755 index 0000000..0fc36a3 --- /dev/null +++ b/test_project/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/test_project/test_project/__init__.py b/test_project/test_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py new file mode 100644 index 0000000..01367ca --- /dev/null +++ b/test_project/test_project/settings.py @@ -0,0 +1,108 @@ +""" +Django settings for test_project project. + +Generated by 'django-admin startproject' using Django 1.8.2. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '(sumlf2_5_f54v-x94!)9ag4bmt-uf6n96m_5#nj%7qas=n7++' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'sheerlike', + 'django_nose', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', +) + +ROOT_URLCONF = 'test_project.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'test_project.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ + +STATIC_URL = '/static/' + +# Set test runner +TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' +NOSE_ARGS = ['--testmatch=(?:^|[\\b_\\.%s-])[Tt]est|^test_', '-s'] diff --git a/test_project/test_project/urls.py b/test_project/test_project/urls.py new file mode 100644 index 0000000..7dc47ea --- /dev/null +++ b/test_project/test_project/urls.py @@ -0,0 +1,21 @@ +"""test_project URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.8/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Add an import: from blog import urls as blog_urls + 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) +""" +from django.conf.urls import include, url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', include(admin.site.urls)), +] diff --git a/test_project/test_project/wsgi.py b/test_project/test_project/wsgi.py new file mode 100644 index 0000000..cb26c81 --- /dev/null +++ b/test_project/test_project/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for test_project project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") + +application = get_wsgi_application() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..bba48d0 --- /dev/null +++ b/tox.ini @@ -0,0 +1,6 @@ +[tox] +envlist=py27,py33,py34 + +[testenv] +deps=-rtest-requirements.txt +commands=python runtests.py From 4d98f4251897e0be0e43a60e95437c24588175a6 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 16:03:01 -0400 Subject: [PATCH 17/34] The actual tests I meant to include in the last commit, plus some administrivia --- AUTHORS | 12 ++++++ ChangeLog | 60 ++++++++++++++++++++++++++++++ TERMS.rst | 56 ++++++++++++++++++++++++++++ sheerlike/test_filters.py | 77 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 AUTHORS create mode 100644 ChangeLog create mode 100644 TERMS.rst create mode 100644 sheerlike/test_filters.py diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..b5e5c7e --- /dev/null +++ b/AUTHORS @@ -0,0 +1,12 @@ +Ans +Anselm Bradford +Chris Contolini +James Wilson +Marc Esher +Ross Karchner +Ross M Karchner +Scott Cranfill +Scott Cranfill +bill shelton +sheltonw +virtix diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c884fde --- /dev/null +++ b/ChangeLog @@ -0,0 +1,60 @@ +CHANGES +======= + +* request.GET.copy( +* Up to date with @kwall's latest changes to sheer.query +* added jinja2 to the requirements +* add elasticsearch depenedency +* remove unneeded import, and fix reference to README in setup.cfg +* trying to get autodoc working +* moved doc stub into docs folder +* product of running sphinx-quickstart +* Update README.rst +* This is madness! This is Python! +* Filtering doesn't work yet, but now it fails differently +* Formatting fixes, and added information on SheerDetailView +* Guidance re: .permalink is no longer accurate +* - restores .permalink (using a registry mechanism) - provides a generic view which should suffice for most uses of sheer's "lookups" feature +* fix typo ;) +* initial commit +* Update README.md +* Update README.md +* Update README.md +* Update README.md +* Update README.md +* Update README.md +* Fix typo +* Update README.md +* Update README.md +* Update README.md +* Update README.md +* Update README.md +* Update README.md +* Update README.md +* Removes trailing newlines +* Update TERMS.md +* Fixes #13 - adds INSTALL.md +* Updating gitignore for more robust coverage +* Fixes typo +* Update README.md +* Add front-endy things to ignore +* Add open source checklist and to do item in README +* Use official CC0 plain text legal code +* Add suggested CHANGELOG format +* Add language about CFPB contribs. to a joint work +* https fix +* https a URL on README +* Fix typo in README +* Update first sentence of TERMS +* Copyedit README +* Copyedit CONTRIBUTING +* Fix typo in TERMS.md +* Fix typos in README. Add simple gitignore +* Update screenshot link to point to CFPB +* Adjust screenshot size +* Add screenshot to README +* Add example screenshot +* Add example screenshot +* Update README and add CHANGELOG +* Add TERMS and CONTRIBUTING +* Initial commit diff --git a/TERMS.rst b/TERMS.rst new file mode 100644 index 0000000..35488a9 --- /dev/null +++ b/TERMS.rst @@ -0,0 +1,56 @@ +As a work of the United States Government, this package (excluding any +exceptions listed below) is in the public domain within the United +States. Additionally, we waive copyright and related rights in the work +worldwide through the `CC0 1.0 Universal public domain +dedication `__. + +Software source code previously released under an open source license +and then modified by CFPB staff or its contractors is considered a +"joint work" (see 17 USC § 101); it is partially copyrighted, partially +public domain, and as a whole is protected by the copyrights of the +non-government authors and must be released according to the terms of +the original open-source license. Segments written by CFPB staff, and by +contractors who are developing software on behalf of CFPB are also in +the public domain, and copyright and related rights for that work are +waived through the CC0 1.0 Universal dedication. + +For further details, please see the CFPB `Source Code +Policy `__. + +CC0 1.0 Universal Summary +------------------------- + +This is a human-readable summary of the `Legal Code (read the full +text) `__. + +No Copyright +~~~~~~~~~~~~ + +The person who associated a work with this deed has dedicated the work +to the public domain by waiving all of his or her rights to the work +worldwide under copyright law, including all related and neighboring +rights, to the extent allowed by law. + +You can copy, modify, distribute and perform the work, even for +commercial purposes, all without asking permission. See Other +Information below. + +Other Information +~~~~~~~~~~~~~~~~~ + +In no way are the patent or trademark rights of any person affected by +CC0, nor are the rights that other persons may have in the work or in +how the work is used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with +this deed makes no warranties about the work, and disclaims liability +for all uses of the work, to the fullest extent permitted by applicable +law. When using or citing the work, you should not imply endorsement by +the author or the affirmer. + +Exceptions +---------- + +*Source code or other assets that are excluded from the TERMS should be +listed here. These may include dependencies that may be licensed +differently or are not in the public domain.* diff --git a/sheerlike/test_filters.py b/sheerlike/test_filters.py new file mode 100644 index 0000000..42e711b --- /dev/null +++ b/sheerlike/test_filters.py @@ -0,0 +1,77 @@ +from django.utils.datastructures import MultiValueDict as MultiDict + +from sheerlike import filters + + +class TestArgParsing(object): + + def setup(self): + self.args = MultiDict([('filter_category', ['cats', 'dogs']), + ('filter_planet', ['earth']), + ('filter_range_date_lte', ['2014-6-1']), + ('filter_range_comment_count_gt', ['100'])]) + + def test_args_to_filter_dsl(self): + filter_dsl = filters.filter_dsl_from_multidict(self.args) + # the existing tests here seemed to depend of the other + # of dictionary keys, which is undefined + + def test_range_args(self): + filter_dsl = filters.filter_dsl_from_multidict(self.args) + assert('range' in filter_dsl[1]) + assert('date' in filter_dsl[1]['range']) + assert('comment_count' in filter_dsl[1]['range']) + assert('2014-6-1' == filter_dsl[1]['range']['date']['lte']) + assert('100' == filter_dsl[1]['range']['comment_count']['gt']) + + def test_filters_for_field(self): + selected = filters.selected_filters_from_multidict( + self.args, 'category') + assert (('cats') in selected) + assert (('dogs') in selected) + + +class TestDateValidation(object): + + def test_date_validation_incorrect_range(self): + args = MultiDict([('filter_range_date_gte', ['2014-6']), + ('filter_range_date_lte', ['2013-6'])]) + filter_dsl = filters.filter_dsl_from_multidict(args) + assert(filter_dsl[0]['range']['date']['gte'] == '2013-6-1') + assert(filter_dsl[0]['range']['date']['lte'] == '2014-6-30') + + def test_date_validation_correct_range(self): + args = MultiDict([('filter_range_date_gte', ['2013-6']), + ('filter_range_date_lte', ['2014-6'])]) + filter_dsl = filters.filter_dsl_from_multidict(args) + assert(filter_dsl[0]['range']['date']['gte'] == '2013-6-1') + assert(filter_dsl[0]['range']['date']['lte'] == '2014-6-30') + + def test_date_validation_with_days_correct_range(self): + args = MultiDict([('filter_range_date_gte', ['2014-1-23']), + ('filter_range_date_lte', ['2014-6-23'])]) + filter_dsl = filters.filter_dsl_from_multidict(args) + assert(filter_dsl[0]['range']['date']['gte'] == '2014-1-23') + assert(filter_dsl[0]['range']['date']['lte'] == '2014-6-23') + + def test_date_validation_with_days_incorrect_range(self): + args = MultiDict([('filter_range_date_gte', ['2014-6-23']), + ('filter_range_date_lte', ['2014-1-23'])]) + filter_dsl = filters.filter_dsl_from_multidict(args) + assert(filter_dsl[0]['range']['date']['gte'] == '2014-1-23') + assert(filter_dsl[0]['range']['date']['lte'] == '2014-6-23') + + def test_default_days_correct_range(self): + args = MultiDict([('filter_range_date_gte', ['2014-1']), + ('filter_range_date_lte', ['2014-6'])]) + filter_dsl = filters.filter_dsl_from_multidict(args) + assert(filter_dsl[0]['range']['date']['gte'] == '2014-1-1') + assert(filter_dsl[0]['range']['date']['lte'] == '2014-6-30') + + def test_default_days_incorrect_range(self): + args = MultiDict([('filter_range_date_gte', ['2014-6']), + ('filter_range_date_lte', ['2014-1'])]) + filter_dsl = filters.filter_dsl_from_multidict(args) + #from nose.tools import set_trace;set_trace() + assert(filter_dsl[0]['range']['date']['gte'] == '2014-1-1') + assert(filter_dsl[0]['range']['date']['lte'] == '2014-6-30') From c7ebebd65e3f360a1009ddc0381fdd24166bae6f Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 16:06:51 -0400 Subject: [PATCH 18/34] Removed screenshot, added mailmap --- .mailmap | 3 +++ screenshot.png | Bin 140237 -> 0 bytes 2 files changed, 3 insertions(+) create mode 100644 .mailmap delete mode 100644 screenshot.png diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..f7deb36 --- /dev/null +++ b/.mailmap @@ -0,0 +1,3 @@ +Ross M Karchner +Ross M Karchner +Scott Cranfill diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index fc37494afc987824df87c16820a083d79eacc3db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140237 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

YM^owaE+U!87ego&7g@5JckCh) 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

q2Zm(pFzLuDitYsUo6EAYQgu$ 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 P9*>HGrhJL41?>L;onT_j From f44e17aa45de95483b840e0b05fcc1107b9f9916 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 16:08:13 -0400 Subject: [PATCH 19/34] Remove duplicate names from AUTHORS --- AUTHORS | 2 -- 1 file changed, 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index b5e5c7e..b817120 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,9 +4,7 @@ Chris Contolini James Wilson Marc Esher Ross Karchner -Ross M Karchner Scott Cranfill -Scott Cranfill bill shelton sheltonw virtix From c3e0b3938f1d70c390f4302abdf97d8cb39ba5a4 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 16:13:15 -0400 Subject: [PATCH 20/34] Removed redundant changelog --- CHANGELOG.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index da5a178..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,17 +0,0 @@ -All notable changes to this project will be documented in this file. -We follow the [Semantic Versioning 2.0.0](http://semver.org/) format. - - -## x.y.z - YYYY-MM-DD - -### Added -- Lorem ipsum dolor sit amet - -### Deprecated -- Nothing. - -### Removed -- Nothing. - -### Fixed -- Nothing. From 19795bcc215d3b40f330ff7c9e6c4970295e638a Mon Sep 17 00:00:00 2001 From: Ross M Karchner Date: Mon, 29 Jun 2015 16:14:45 -0400 Subject: [PATCH 21/34] Update README.rst --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 0abef36..418301e 100644 --- a/README.rst +++ b/README.rst @@ -123,8 +123,8 @@ Recommendations Open source licensing info -------------------------- -1. `TERMS `__ -2. LICENSE +1. `TERMS `__ +2. `LICENSE `__ 3. `CFPB Source Code Policy `__ From 2b76f6ace0aa689c9638279f37c88214fefe3b72 Mon Sep 17 00:00:00 2001 From: Ross M Karchner Date: Mon, 29 Jun 2015 16:26:30 -0400 Subject: [PATCH 22/34] Update README.rst --- README.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.rst b/README.rst index 418301e..df510ce 100644 --- a/README.rst +++ b/README.rst @@ -110,6 +110,24 @@ API's and RSS Feeds We will need to switch to native Django tools for such things. +See it in action +================ + +Want to test this out? + +These instructions assume you have a local elasticsearch server, already populated by 'sheer index' as documented in the `cfgov-refresh readme `__. + +- Check out `cfgov-django `__ alongside cfgov-refresh. +- create a new virtualenv and pip install -r requirements.txt +- cd into the 'cfgov' directory, and run './manage.py runserver' + +You should then be able to see the site running on http://localhost:8000 + +Run the tests +============= + +Install `tox ` and run the 'tox' command from a checkout of this repo. + Recommendations =============== From b56675294d657373e940fd996a987a02a147bce6 Mon Sep 17 00:00:00 2001 From: Ross M Karchner Date: Mon, 29 Jun 2015 16:26:56 -0400 Subject: [PATCH 23/34] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index df510ce..eb32b3c 100644 --- a/README.rst +++ b/README.rst @@ -126,7 +126,7 @@ You should then be able to see the site running on http://localhost:8000 Run the tests ============= -Install `tox ` and run the 'tox' command from a checkout of this repo. +Install `tox `__ and run the 'tox' command from a checkout of this repo. Recommendations =============== From 2ff1f9810e35793b390987054591cf2d2eba2f00 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 29 Jun 2015 16:27:52 -0400 Subject: [PATCH 24/34] One last markdown-to-ReStructuredText conversion --- CONTRIBUTING.md | 32 ------------------ opensource-checklist.md | 73 ----------------------------------------- 2 files changed, 105 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 opensource-checklist.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 059f2ec..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,32 +0,0 @@ -# Guidance on how to contribute - -> All contributions to this project will be released under the CC0 public domain -> dedication. By submitting a pull request or filing a bug, issue, or -> feature request, you are agreeing to comply with this waiver of copyright interest. -> Details can be found in our [TERMS](TERMS.md) and [LICENCE](LICENSE). - - -There are two primary ways to help: - - Using the issue tracker, and - - Changing the code-base. - - -## Using the issue tracker - -Use the issue tracker to suggest feature requests, report bugs, and ask questions. -This is also a great way to connect with the developers of the project as well -as others who are interested in this solution. - -Use the issue tracker to find ways to contribute. Find a bug or a feature, mention in -the issue that you will take on that effort, then follow the _Changing the code-base_ -guidance below. - - -## Changing the code-base - -Generally speaking, you should fork this repository, make changes in your -own fork, and then submit a pull-request. All new code should have associated unit -tests that validate implemented features and the presence or lack of defects. -Additionally, the code should follow any stylistic and architectural guidelines -prescribed by the project. In the absence of such guidelines, mimic the styles -and patterns in the existing code-base. diff --git a/opensource-checklist.md b/opensource-checklist.md deleted file mode 100644 index c4dca71..0000000 --- a/opensource-checklist.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: base -title: "Open Source Checklist" ---- - -# Open Source Check List - -Prior to releasing a project to GitHub.com, walk through these items and ensure they are addressed. - -- **Has PII been removed?** - - Use [Clouseau](https://github.com/virtix/clouseau) for scanning source code. - - For an Open Source Release, attach the Clouseau output. - - If there are images, visually inspect each image to ensure there is no CFPB-specific information. - -- **Have security vulnerabilities been remediated?** - - Use the [OWASP Top 10](https://www.owasp.org/index.php/Top_10_2013) - - [National Vulnerability Database](http://nvd.nist.gov/) - - [SANS Swat Checklist](http://www.securingthehuman.org/developer/swat) - -- **Are we including any other open source products? If so, is there any conflict with our public domain release?** - -- **Is our `TERMS.md` included?** - -- **Is a `CHANGELOG.md` present and does it contain structured, consistently formatted recent history?** - - See and - - Some Inspiration: - -- **Are instructions for contributing included (`CONTRIBUTING.md`)?** - -- **Are installation instructions clearly written in the `README` _and_ tested on a clean machine?** - -- **Are all dependencies described in the `README`, `requirements.txt`, and/or `buildout.cfg`?** - -- **Are the API docs generated?** - -- **Are there unit tests?** - -- **If appplicable and possible, is it set up in TravisCI?** - -- **Have multiple people reviewed the code?** - -- **Is there a screenshot in the `README`, if applicable?** - - -## Copy this version to paste into a GitHub issue with live checkboxes: - -~~~ -- [ ] **Has PII been removed?** - - Use [Clouseau](https://github.com/virtix/clouseau) for scanning source code. - - If there are images, visually inspect each image to ensure there is no CFPB-specific information. -- [ ] **Have security vulnerabilities been remediated?** -- [ ] **Are we including any other open source products? If so, is there any conflict with our public domain release?** -- [ ] **Is our `TERMS.md` included?** -- [ ] **Is a `CHANGELOG.md` present and does it contain structured, consistently formatted recent history?** -- [ ] **Are instructions for contributing included (`CONTRIBUTING.md`)?** -- [ ] **Are installation instructions clearly written in the `README` _and_ tested on a clean machine?** -- [ ] **Are all dependencies described in the `README`, `requirements.txt`, and/or `buildout.cfg`?** -- [ ] **Are the API docs generated?** -- [ ] **Are there unit tests?** -- [ ] **If applicable and possible, is it set up in TravisCI?** -- [ ] **Have multiple people reviewed the code?** -- [ ] **Is there a screenshot in the `README`, if applicable?** -~~~ - ----- - - -## Take a look at the following projects as good models to follow: - - - [https://github.com/cfpb/qu](https://github.com/cfpb/qu) - - [https://github.com/cfpb/idea-box](https://github.com/cfpb/idea-box) - - [https://github.com/cfpb/hmda-tool](https://github.com/cfpb/hmda-tools) - - [https://github.com/cfpb/django-cache-tools](https://github.com/cfpb/django-cache-tools) From 2f2898d929ab2f4b36b7058e8a2719bca080a149 Mon Sep 17 00:00:00 2001 From: Ross M Karchner Date: Mon, 29 Jun 2015 18:15:54 -0400 Subject: [PATCH 25/34] Update README.rst --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index eb32b3c..3374b90 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,9 @@ django-sheerlike ================ +.. image:: https://travis-ci.org/cfpb/django-sheerlike.svg + :target: https://travis-ci.org/cfpb/django-sheerlike + This is an attempt to port some of our favorite `sheer `__ features over to Django. From 9a5a639bd83616550498a5c8fa60e9b18b24abc8 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 30 Jun 2015 12:54:49 -0400 Subject: [PATCH 26/34] A new generic view that infers templates from the URL, some configuration simplification and DRY work, the beginning of a tool for populating a Django model from a Sheer template --- AUTHORS | 2 +- CONTRIBUTING.rst | 33 ++++++++++++++ ChangeLog | 11 ++++- requirements.txt | 1 + sheerlike/__init__.py | 15 ++++++- sheerlike/management/__init__.py | 0 sheerlike/management/commands/__init__.py | 0 sheerlike/management/commands/runindexer.py | 49 +++++++++++++++++++++ sheerlike/query.py | 5 ++- sheerlike/views/generic.py | 14 ++++++ 10 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 CONTRIBUTING.rst create mode 100644 sheerlike/management/__init__.py create mode 100644 sheerlike/management/commands/__init__.py create mode 100644 sheerlike/management/commands/runindexer.py diff --git a/AUTHORS b/AUTHORS index b817120..51940ad 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,7 +3,7 @@ Anselm Bradford Chris Contolini James Wilson Marc Esher -Ross Karchner +Ross M Karchner Scott Cranfill bill shelton sheltonw diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..44c4b91 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,33 @@ +Guidance on how to contribute +============================= + + All contributions to this project will be released under the CC0 + public domain dedication. By submitting a pull request or filing a + bug, issue, or feature request, you are agreeing to comply with this + waiver of copyright interest. Details can be found in our + `TERMS `__ and `LICENCE `__. + +There are two primary ways to help: - Using the issue tracker, and - +Changing the code-base. + +Using the issue tracker +----------------------- + +Use the issue tracker to suggest feature requests, report bugs, and ask +questions. This is also a great way to connect with the developers of +the project as well as others who are interested in this solution. + +Use the issue tracker to find ways to contribute. Find a bug or a +feature, mention in the issue that you will take on that effort, then +follow the *Changing the code-base* guidance below. + +Changing the code-base +---------------------- + +Generally speaking, you should fork this repository, make changes in +your own fork, and then submit a pull-request. All new code should have +associated unit tests that validate implemented features and the +presence or lack of defects. Additionally, the code should follow any +stylistic and architectural guidelines prescribed by the project. In the +absence of such guidelines, mimic the styles and patterns in the +existing code-base. diff --git a/ChangeLog b/ChangeLog index c884fde..0c40906 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,16 @@ CHANGES ======= -* request.GET.copy( +* One last markdown-to-ReStructuredText conversion +* Update README.rst +* Update README.rst +* Update README.rst +* Removed redundant changelog +* Remove duplicate names from AUTHORS +* Removed screenshot, added mailmap +* The actual tests I meant to include in the last commit, plus some administrivia +* Migrated some unit tests from Sheer, and configs for tox and Travis +* request.GET.copy() doesn't get you a mutable object * Up to date with @kwall's latest changes to sheer.query * added jinja2 to the requirements * add elasticsearch depenedency diff --git a/requirements.txt b/requirements.txt index 73cec4a..3b0c147 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +unipath>=1.1,<=2.0 elasticsearch==1.6.0 Jinja2==2.7.3 python-dateutil==2.4.2 diff --git a/sheerlike/__init__.py b/sheerlike/__init__.py index 9d7a83b..82ebea9 100644 --- a/sheerlike/__init__.py +++ b/sheerlike/__init__.py @@ -4,9 +4,12 @@ from django.contrib.staticfiles.storage import staticfiles_storage from django.core.urlresolvers import reverse +from django.conf import settings from jinja2 import Environment +from unipath import Path + from .query import QueryFinder, more_like_this, get_document from .filters import selected_filters_for_field, is_filter_selected from .templates import date_formatter @@ -30,8 +33,16 @@ def date_filter(value, format="%Y-%m-%d"): def environment(**options): queryfinder = QueryFinder() - # Django defaults to DebugUndefined - # options['undefined'] = make_logging_undefined() + searchpath =[] + + sites = settings.SHEER_SITES + for site in sites: + site_path = Path(site) + searchpath.append(site_path) + searchpath.append(site_path.child('_includes')) + searchpath.append(site_path.child('_layouts')) + + options['loader'].searchpath += searchpath env = Environment(**options) env.globals.update({ diff --git a/sheerlike/management/__init__.py b/sheerlike/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sheerlike/management/commands/__init__.py b/sheerlike/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sheerlike/management/commands/runindexer.py b/sheerlike/management/commands/runindexer.py new file mode 100644 index 0000000..4b74387 --- /dev/null +++ b/sheerlike/management/commands/runindexer.py @@ -0,0 +1,49 @@ +import sys +import os.path +import codecs +import json + +from importlib import import_module + +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings + +from unipath import Path + +class Command(BaseCommand): + help = "populate a Django model using a Sheer indexer" + + + def add_arguments(self,parser): + parser.add_argument('indexer_name') + parser.add_argument('target_model') + + def handle(self, *args, **options): + indexer_name = options ['indexer_name'] + + sheer_sites = settings.SHEER_SITES + sheer_libs = [Path(s).child('_lib') for s in sheer_sites] + sys.path += sheer_libs + + processors = {} + + possible_processor_configs =\ + [Path(s).child('_settings').child('processors.json') for s in sheer_sites] + + for procjson in possible_processor_configs: + if os.path.exists(procjson): + with codecs.open(procjson, encoding='utf8') as jsonfile: + raw_json = jsonfile.read() + merged_json = os.path.expandvars(raw_json) + config = json.loads(merged_json) + processors.update(config) + + if indexer_name in processors: + mod = import_module(processors[indexer_name]['processor']) + generator = mod.documents(indexer_name, **processors[indexer_name]) + + for doc in generator: + self.stdout.write(str(doc)) + + else: + raise CommandError('could not find a processor for %s' % indexer_name) diff --git a/sheerlike/query.py b/sheerlike/query.py index 60d8f66..8d6ca49 100644 --- a/sheerlike/query.py +++ b/sheerlike/query.py @@ -17,6 +17,8 @@ import elasticsearch +from unipath import Path + #from sheer.utility import find_in_search_path from .filters import filter_dsl_from_multidict @@ -273,9 +275,10 @@ class QueryFinder(object): def __init__(self): self.es = elasticsearch.Elasticsearch(settings.SHEER_ELASTICSEARCH_SERVER) self.es_index = settings.SHEER_ELASTICSEARCH_INDEX + self.searchpath = [Path(site).child('_queries') for site in settings.SHEER_SITES] def __getattr__(self, name): - for dir in settings.SHEER_QUERIES_DIRS: + for dir in self.searchpath: query_filename = name + ".json" query_file_path = os.path.join(dir, query_filename) diff --git a/sheerlike/views/generic.py b/sheerlike/views/generic.py index 5157a4e..aefdfd4 100644 --- a/sheerlike/views/generic.py +++ b/sheerlike/views/generic.py @@ -5,6 +5,20 @@ from sheerlike.query import get_document +class SheerTemplateView(TemplateView): + def get_template_names(self,*args, **kwargs): + # if template_name is configured, just do that + if self.template_name is not None: + return [self.template_name] + + # otherwise, try to infer a template name from + # the url + request = self.request + if request.path.endswith('/'): + return [(request.path[1:]+'index.html')] + else: + return [request.path[1:]] + class SheerDetailView(TemplateView): doc_type = None local_name = 'object' From aee686a430c50d374936a31454d8c99f4354066c Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 6 Jul 2015 09:31:01 -0400 Subject: [PATCH 27/34] refactored to simplify and unify the generic views, and make the combined SheerTemplateView behave more like Sheer does. --- sheerlike/__init__.py | 3 ++ sheerlike/views/generic.py | 58 +++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/sheerlike/__init__.py b/sheerlike/__init__.py index 82ebea9..03e9865 100644 --- a/sheerlike/__init__.py +++ b/sheerlike/__init__.py @@ -34,6 +34,7 @@ def environment(**options): queryfinder = QueryFinder() searchpath =[] + staticdirs = [] sites = settings.SHEER_SITES for site in sites: @@ -41,8 +42,10 @@ def environment(**options): searchpath.append(site_path) searchpath.append(site_path.child('_includes')) searchpath.append(site_path.child('_layouts')) + staticdirs.append(site_path.child('static')) options['loader'].searchpath += searchpath + settings.STATICFILES_DIRS = staticdirs env = Environment(**options) env.globals.update({ diff --git a/sheerlike/views/generic.py b/sheerlike/views/generic.py index aefdfd4..08e0f13 100644 --- a/sheerlike/views/generic.py +++ b/sheerlike/views/generic.py @@ -1,37 +1,57 @@ from django.views.generic.base import TemplateView from django.http import Http404 +from django.template import TemplateDoesNotExist from elasticsearch import TransportError from sheerlike.query import get_document class SheerTemplateView(TemplateView): + doc_type = None + local_name = 'object' + default_template = None + def get_template_names(self,*args, **kwargs): - # if template_name is configured, just do that - if self.template_name is not None: + if self.template_name: return [self.template_name] - # otherwise, try to infer a template name from - # the url request = self.request + templates = [] + if request.path.endswith('/'): - return [(request.path[1:]+'index.html')] + templates.append(request.path[1:]+'index.html') else: - return [request.path[1:]] + templates.append(request.path[1:]) + if 'doc_id' in self.kwargs and self.default_template: + templates.append(self.default_template) -class SheerDetailView(TemplateView): - doc_type = None - local_name = 'object' + return templates def get_context_data(self, **kwargs): - doc_id = kwargs.pop('doc_id') - context = super(SheerDetailView, self).get_context_data(**self.kwargs) + context = super(SheerTemplateView, self).get_context_data(**self.kwargs) + if 'doc_id' in kwargs: + doc_id = kwargs.pop('doc_id') + self.doc_id = doc_id + try: + document = get_document(doctype=self.doc_type, + docid=doc_id) + context[self.local_name] = document + + return context + except TransportError: + pass + + return context + + def render_to_response(self, context, **response_kwargs): + response = super(SheerTemplateView,self).render_to_response(context, **response_kwargs) try: - document = get_document(doctype=self.doc_type, - docid=doc_id) - context[self.local_name] = document - - return context - except TransportError: - raise Http404("Document does not exist") - + template = response.resolve_template(response.template_name) + + except TemplateDoesNotExist: + raise Http404("could not find template " + response.default_template) + + if template.template.name == self.default_template and self.local_name not in context: + raise Http404('fell back to %s, but %s with id %s not found' % + (self.default_template, self.doc_type, self.doc_id)) + return response From 843c868621be52a9f05defbae6fe2938a16c3295 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 6 Jul 2015 09:32:58 -0400 Subject: [PATCH 28/34] corrected README re: generic views --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3374b90..db95b97 100644 --- a/README.rst +++ b/README.rst @@ -46,10 +46,10 @@ pretty simple: .. code:: python - url(r'^blog/(?P[\w-]+)/$', SheerDetailView.as_view( + url(r'^blog/(?P[\w-]+)/$', SheerTemplateView.as_view( doc_type='posts', local_name='post', - template_name='blog/_single.html', + default_template='blog/_single.html', ), name='blog_detail'), Almost all of the rest of the sheer machinery is still intact, though: From a93dadb3ee8c210a31d8a99641aa1ec9d64a68cb Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 6 Jul 2015 11:23:54 -0400 Subject: [PATCH 29/34] fixed logic for certain types of 404's --- sheerlike/views/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sheerlike/views/generic.py b/sheerlike/views/generic.py index 08e0f13..92835ee 100644 --- a/sheerlike/views/generic.py +++ b/sheerlike/views/generic.py @@ -49,7 +49,7 @@ def render_to_response(self, context, **response_kwargs): template = response.resolve_template(response.template_name) except TemplateDoesNotExist: - raise Http404("could not find template " + response.default_template) + raise Http404("could not find template in " + str(response.template_name)) if template.template.name == self.default_template and self.local_name not in context: raise Http404('fell back to %s, but %s with id %s not found' % From 0acdb4b701d1d67472de95297cffe6ad11a134f5 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Mon, 13 Jul 2015 23:30:31 -0400 Subject: [PATCH 30/34] fix date formatting filter, and implement relative template imports, and a global request object! --- sheerlike/__init__.py | 37 ++++++++++++++++++++++++++++++++++--- sheerlike/middleware.py | 3 +++ sheerlike/templates.py | 9 ++++++--- sheerlike/views/__init__.py | 6 ++++++ 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/sheerlike/__init__.py b/sheerlike/__init__.py index 03e9865..9827606 100644 --- a/sheerlike/__init__.py +++ b/sheerlike/__init__.py @@ -1,12 +1,17 @@ from __future__ import absolute_import # Python 2 only +import os +import os.path import functools +import warnings from django.contrib.staticfiles.storage import staticfiles_storage from django.core.urlresolvers import reverse from django.conf import settings from jinja2 import Environment +import jinja2.runtime +from jinja2.runtime import Context from unipath import Path @@ -26,10 +31,36 @@ def url_for(app, filename): else: raise ValueError("url_for doesn't know about %s" % app) -def date_filter(value, format="%Y-%m-%d"): - return date_formatter(value, format) +def date_filter(value, format="%Y-%m-%d", tz="America/New_York"): + return date_formatter(value, format, tz) +class SheerlikeContext(Context): + def __init__(self, environment, parent, name, blocks): + super(SheerlikeContext, self).__init__(environment, parent, name, blocks) + self.vars['request'] = get_request() + +# Monkey patch not needed in master version of Jinja2 +# https://github.com/mitsuhiko/jinja2/commit/f22fdd5ffe81aab743f78290071b0aa506705533 +jinja2.runtime.Context = SheerlikeContext + +class SheerlikeEnvironment(Environment): + def join_path(self, template, parent): + dirname = os.path.dirname(parent) + segments = dirname.split('/') + paths = [] + collected = '' + for segment in segments: + collected += segment + '/' + paths.insert(0,collected[:]) + for p in paths: + relativepath = os.path.join(p, template) + for search in self.loader.searchpath: + filesystem_path = os.path.join(search, relativepath) + if os.path.exists(filesystem_path): + return relativepath + return template + def environment(**options): queryfinder = QueryFinder() @@ -47,7 +78,7 @@ def environment(**options): options['loader'].searchpath += searchpath settings.STATICFILES_DIRS = staticdirs - env = Environment(**options) + env = SheerlikeEnvironment(**options) env.globals.update({ 'static': staticfiles_storage.url, 'url_for':url_for, diff --git a/sheerlike/middleware.py b/sheerlike/middleware.py index 057bf4a..15fc1c9 100644 --- a/sheerlike/middleware.py +++ b/sheerlike/middleware.py @@ -1,5 +1,6 @@ from threading import local from django.http import HttpResponse +from django.core.urlresolvers import resolve _active = local() @@ -25,4 +26,6 @@ def process_view(self, request, view_func, view_args, view_kwargs): request.headers = FlaskyHeaderGetter(request) request.url = "%s://%s%s" % (request.scheme, request.get_host(), request.get_full_path()) + request.url_rule = request.resolver_match + request.url_rule.endpoint = request.resolver_match.url_name return None diff --git a/sheerlike/templates.py b/sheerlike/templates.py index dbd87c6..95d8d3f 100644 --- a/sheerlike/templates.py +++ b/sheerlike/templates.py @@ -1,10 +1,13 @@ import datetime from dateutil import parser +from pytz import timezone - -def date_formatter(value, format="%Y-%m-%d"): +def date_formatter(value, format="%Y-%m-%d", tz='America/New_York'): if type(value) not in [datetime.datetime, datetime.date]: - dt = parser.parse(value, default=datetime.date.today().replace(day=1)) + date = parser.parse(value, default=datetime.datetime.today().replace(day=1)) + naive = date.replace(tzinfo=None) + dt = timezone(tz).localize(naive) + print dt.tzinfo else: dt = value diff --git a/sheerlike/views/__init__.py b/sheerlike/views/__init__.py index 91ea44a..7d8431d 100644 --- a/sheerlike/views/__init__.py +++ b/sheerlike/views/__init__.py @@ -1,3 +1,9 @@ from django.shortcuts import render # Create your views here. + +def server_error(request, template_name='404.html'): + from django.template import RequestContext + from django.http import HttpResponseServerError + t = get_template(template_name) + return HttpResponseServerError(t.render(RequestContext(request))) From b3546272a9a3a4eb93a356440bae49d88293478a Mon Sep 17 00:00:00 2001 From: Ross M Karchner Date: Mon, 13 Jul 2015 23:33:16 -0400 Subject: [PATCH 31/34] Update README.rst --- README.rst | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index db95b97..5fea441 100644 --- a/README.rst +++ b/README.rst @@ -59,24 +59,10 @@ functions like get\_document and more\_like\_this. Template tweaks --------------- -Eliminate relative template includes/imports. for example, in -(cfgov-refresh) blog/index.html: - -``{% import "_vars-blog.html" as vars with context %}`` - -becomes ``{% import "blog/_vars-blog.html" as vars with context %}`` - -The request object is a context variable now, so in order to reference -it in 'imported' templates, `you must specify 'with -context' `__. - -For example, ``{% from "macros.html" import share as share %}`` becomes -``{% from "macros.html" import share as share with context%}`` - -Also, the `Django request +The `Django request object `__ has different properties and methods than the one available in -Flask/sheer. +Flask/sheer. We've added some helpers to support existing access patterns, but those should be considered deprecated. Inline IF statements MUST have an else clause, `otherwise the output is undefined `__ From d0084b4dc90a3cf3409b43d220608b17d545949a Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 14 Jul 2015 00:44:07 -0400 Subject: [PATCH 32/34] add pytz --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 3b0c147..ba44c61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ unipath>=1.1,<=2.0 elasticsearch==1.6.0 Jinja2==2.7.3 python-dateutil==2.4.2 +pytz From 6b2fb741f16592ab75ee893de0d0e56b426d8f79 Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Tue, 14 Jul 2015 00:45:58 -0400 Subject: [PATCH 33/34] delete unneccesary print statement --- sheerlike/templates.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sheerlike/templates.py b/sheerlike/templates.py index 95d8d3f..bd62374 100644 --- a/sheerlike/templates.py +++ b/sheerlike/templates.py @@ -7,7 +7,6 @@ def date_formatter(value, format="%Y-%m-%d", tz='America/New_York'): date = parser.parse(value, default=datetime.datetime.today().replace(day=1)) naive = date.replace(tzinfo=None) dt = timezone(tz).localize(naive) - print dt.tzinfo else: dt = value From 59a3c2d26b9e1d7bba94a467563d9030de5c612d Mon Sep 17 00:00:00 2001 From: Ross Karchner Date: Wed, 22 Jul 2015 15:52:02 -0400 Subject: [PATCH 34/34] git ignore fix via @kurtw --- .gitignore | 1 + ChangeLog | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 89e446b..9d11abe 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ __pycache__/ *.py[cod] .env .eggs/ +.egg/ # Django # ################# diff --git a/ChangeLog b/ChangeLog index 0c40906..8cb76be 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,15 @@ CHANGES ======= +* delete unneccesary print statement +* add pytz +* Update README.rst +* fix date formatting filter, and implement relative template imports, and a global request object! +* fixed logic for certain types of 404's +* corrected README re: generic views +* refactored to simplify and unify the generic views, and make the combined SheerTemplateView behave more like Sheer does +* A new generic view that infers templates from the URL, some configuration simplification and DRY work, the beginning of a tool for populating a Django model from a Sheer template +* Update README.rst * One last markdown-to-ReStructuredText conversion * Update README.rst * Update README.rst

4lNMc9OHXR&+$)-K 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%0DPl~lFG z19nAUQz5}85GuMKzD+RXi`XA4(|%RZr?PADg;i%rkuhc!4hMo&!be#WvgL9gXQ+?M z=2_;O%I<80-)hBcshg&4I~1zaC5<|Gc3NIwng7`TKJCxmgqeqCx1NMsTfzzZ%qCNN zK7<=5o_B5Ywlp8(gW=oJTSoa-x>*IC(=2H$!zc`hxMB`BqV1 zJ8U~kJ=$6I6v3e88MK=mSfUDSEqV`YX zX3i`w0Z%cZ0%O__(heg%ydQ2{+z~uCByMa?GHN*fLc9_EKQ?=|i$Km(62hY%wgxc` z`f|2M`I!No-F{g+lW5FIa|}^)qh0EO8xm0`g$Gi0Hs@o*LC!1wFebD01%UTVM~0D_ zPiN|HYU2_bT-10!zfFcr^OLSsZHAa~+wDot=+Hp%_suxI&uyS2T!!!*kp-Vw8C4X= z;K?I1>lKVOjf&%aB0-i|$diXthA0}>#+pmA?oTq8ZYm*h_B=xgai<+D6>nhrS%p<9 z#kKau{i0sgkutZ zxS}iP*kud4*}`qCjDZY%6vZrRn6-Z|pDMoB+VRNPRN?fOCW!?j07COQ3Ukcfgz%p@ zd<*TPwbd9_6x!=#s&Yut848oAfgM#a<)b0{Yf2(l&SQd65vR1wbVN)&lH+iSO_1XZ zYT<2o=85fX{kvsXae_O~(s=}>;T36(2Mz+?H<<73f1IUCOg!F~hif|r1NN8ns|k1EnP>E4EVAHk4yJn9mc9q^{R&QY zKO^p+8cM1>MLsT(V9W<4pZX%-1fQRy3 z2lo|$38N?rE?$-22>3n#a8vPjwXQupQkDmH#cpGDP&Q^t5&=8NEp&@kigfXoFH{nk5 zs#Njv{RY;-bamfsXZu#o(ALyc|4xK89*<+}C9OVsXk>wiU+@Y(!U|gJ?E0+#2&2ZO z=gW4?w14_QpEUCx+F@~nnp1p^WSB&)7>aPBeVHf=xds3uyswUW;MuP6s$=jXNaq&G zE+EpE6knc$b{o0l`s*k%{FTN=4ZMg&bpH7r)flmHNl8VIr9sLi19n{21Um&DprzgZ zQynXnb+Tw@o`8lj#_>QJv+>6)!?9_Co+Q5or|1C8!`1?`PZh?T6ZPo3UGS$$YGML! zf{G!~vjx$XOvhM8U5zair5JixSXeThfnp&}m|f4M?YJm@+!v+2N&ThYl<`_{1lC3y zM295RDc(gY9j`lmpr+0eIhw#;FmMx8LY;mz?=IpP-)a8TJ(Rz|N}v*;WQO`}j4V$C zQ)C|Ck~y$#M`)+56|UFrM#QQZ5@JGEqN5a5wIP(!4~9ndkW!2VI-wgtU59Vf1ss&{!e9V~Iw+H@e+T6KMkAa>--M75t`@ z1B0J*Ku40rex-!!s|AyYV8hvP+{^cYw9OzsKbV_7IgdCF71umebI1UCNw{nCi=2^# zkafDwmT(XagZK5@l^4A z;D)chC@S*mDAK?hk@)z%xC9mDC_3`IV!h*3;;(}X7x$hrv4dO8$JK+LH||^R9n~hN z28qa(QpxyH9L#zn*s!jP8Z^|4lB=~KLLiq;bXwgC-Dr_=2q&;?H`dfe2U@bXBguyQ z*mUl%3+Y0@BY+1>^GLo4#8LHF`qPqLwnG~WuY5EC=!FRpX!^K?VS4#!q(dm|-!&UC zZcnLa=GLQ&^{ob@mhAdIrMOtud{b&5156m-{LLo#hwh{f4WwML2Q%a~1Ccc=5E5=c zv+Q!Q7P$z^lj+4ATRr+c8*`?g!qSLtXALE;yt z)O|T!q!*#A#H_w^HLBr3eO&KCd(iy$z^4{NqQ7Tc^}S|XM7W>FGXR4n?=50MhHCXb z_ebo`u4vIWW3%c;1L(LJO8Yg|7R&FNn<#x_a?LYGjwyL_=bS5?L21FKifSqWDp3+g zsteV~v_@CT`hS_izvrJ@yrHoMgmwmCq}V=dBhlAc?H&5s^Arm_{Q8T_S{1 zL;-W_s$q}DfHzP$)}&0;J2h^{tW zZ}$^)7Ce2YKm!vKsiZhB+dL<{>6_@4xk2T5IH}(qYYt*uWGMD;1n29S9wwLR4j8IE zW^!9IxuljAn=wSaMZma6 zhhfnUA^_m92+md|%URKN8OUe+E-0`1Jc7Hux;O-Bj$22_9bFcPE0C0nfY6qp1R0Cg zGuPO-TpLWa{O!s7tLPK%`xm`-OOIsuX7ZpSeSt9H<0rDye8`!xc`U|KJyOCNJs(X*D&K_(hR)f#G6beQt7*c%+@CfX|5-D zs4*vG($%(Z)rh3_b*6Q_spEAS3sHkMRlRFw%3%{ zp(oL%!P=xyw`3u!GH8Z~!Is6H*XBji;Ex?H&0sM=L8O{CE{mn`6eC%joW}86ASW$O z<=1AS7R$4dwi`1YXlN+@DgW~o=?eeX1(9~qVX^FuqYc!@j_~cnofLV8+Hu3GBJ61cCq1B%CYI9%UFU8 z>yzOKIU6mDER1XZ7#bX}(hZlo>gQka7BiMif~3O^u@4rc-_{&%*?+(DaXJ>^(y;5s zl<~f>Y)9Fx14ec4{RKNroV3loPeF1r#eX{=k15Px^SHJKll`vRsnJ|UJmYQ4&)|cS7yU4er5f$0&r4* zBBv?IishN2VAhecYpak?>YAiEE6X2D9G{@~kqm9d$4_*)busR}?aR;k^%|zo+?n#) z;V3F_5Mawd!B5aoQ`5UQtG#M)fhujtoZH%_?KyLMkxdUnU&_#Aqqi~yr^<@exb&B4 zeKB4;8*=Xra2<&_Ma*@aI0DGWOzD;Ka-xpG#Lv+#VS`CGr8bBN>ld7^F!?}>a|L?h zzKXw61;?AIq_n$<9;N;gs70(}aE)FfZJnSbD{bS7hYV4QNRJ#;RIX-dzOc__Dzo`HXVxrsn5?NSS*iE%v^*|pRq!ei%Ut&5v@ZL1( zT@PZt7Va@hV^>#QiUpa!1mJr3&)ACNvxSY!uo5K$O3>sJA3tR9Q{ncivHzdDnIm;5 z)dwVZS(FXAlY&~{@^#`WDVqF`l>2Xifk9RTKSc>BE&E?+^Y3FK=+Co(7hwNw7j?)p zYxla#da9j2+=+=kz|Ey23@3wPx&Mk<{su(JnEnOh_$F+R`{w}b_jSNeN5qKE>{>p2 zg0T@FZqr|m|9$A}v)+Jg(&Tuv$BcT&S@JuuqnwARV625Aijiv`BoBJgWu#>Hzh7HM zVhl?d{wxC#_VD;<(9~5Df(}{=4Lyp|Z6jZ-!o!<6Pc2rXtfaE~-wR^IM9SqE8F=f% zYRYPo>l5e;5(}yBFrZF<$jLQ@Ys{rt4pT}YUG@Ks*wH}ki9tyw)1>fkG1#li)S-2e z{)*3#zL&-RGe=VJLG$EOsP95Bq&H$mCaS0t|Hk?)5lEjSM%|`aR{q&;&qDZ5qzV|C zXkZznCauQdPu1$b+$Q$tsM?5!sY3qKaffSYEb&znwm8$#T9^Twv%j(cO7JQ-@C3q& z^7pT0AAc7i4RX*t5?0y9sLN5X)QhJ*@6uQ8PASr>Tl#9iuu87#>Sv}?SWv2l{wu$l z=082izy*m@&Lr;yl8~{O#~C`t+r&?ek(%v!XZWcUIxmhVx|3k1S8Z60%g@G?uBZ?H zUFcGgQ{oIqMR20TEvlsU|A*dJ|N2jn)=a6Y|FwtixV?8V1_laQla31Zh)fsf;lY#X zYLzL(UuJjDYw*6;|P!x78hTbExRdC$dG;1hAn7oOB2qnuC9yjAPNrb^2E*_`g>ap$kZI0Bb6NPnVW2tJS`y9{c;=v}s@^Yf=DMvA&^l7G<4zZULK zg8)LTE+QgwH)=hrZ)r?X1QgzKBL1{DONyplfRt&|2ePnA!+}RTPUbYt*&?^yNS=iV;_S{b#57v>2VJpSc(rqX+wy7olYa#6)w{ zQu9)k%E-#)(Em@@%>@mq6SwwAKFfB9i=O}cg@1iG$xjVso8%X@|0dV}z>NQEdCVl% z2LE>G6ZFqE#Yq1C1ZYXb|MiI!z)x*4PHg0V#A$z_(7*>BlI}0%u2LtI502n|P$&I- zO(&}6^Edwo#fB63lw6*vRi{(VeUd>CO$A7xi$Vv7`R@N=>KnuB+@7}^+eTyCwylOc zW@Fp7&BkbKTRUjn*ftyU&FT4_|Ml+A&wjAhtb26NjKTISyckNQY*1mj`2VhD00ZT5 zi8Kc83J%Ch0umB_l!P!w0FAK;t_p1DVp>lRopX^<81t zZOSHWz6FXG5xn-`)2leS^mp2z(JW%`Rx7N-rZeL&$#<0^)qxo_a!7Yy%O*3}k(iBl zA1aC-6y8gyvpGko@)C(0D&R9VBWM`jEFrg;&6BYM10A(W3pdVdfnv_WYg@+Y6zQ*# z|LhxYz-B9~-u^;=85Jr1m?~O{``=mmBi>6ygBqnIvfrvx?WCF@M1~$RVc37l_=<{W z*GX9OJGZZ71s3vvotSGWbo2E!>~W@WRk`8%vl5XM$zb9Y^_D%iS5)wsk*}!%M0P