Bump to version 0.2 (retroactively tagging the previous version as 0.1).
The flask icon is from DesignKode
From their website [1]:
DesignKode is releasing this set of 40 free high quality icons for
your web site and application GUI designs. All icons in this set are
32 x 32 pixel PNG image files. You may freely use these icons in your
commercial or personal projects without attribution.
[1] http://www.designkode.com/blog/free-developer-icons
+MANIFEST
+sqlite3.db
+dist
+build
*.pyc
-recipe
-mom*
+++ /dev/null
-guest:cookbook:8eb3d4841608f011af3dd4bdc4c8b340
-Cookbook provides a Python interface to your recipes so you can throw
-out all those index cards.
+Cookbook is a recipe manager written in Python_ using the Django_
+framework.
-The entry script:
- ./bin/cook.py
-fires up the server, binding it to a local interface/port. It also
-translates cookbook formats, e.g. from my mom's rough text file to the
-more robust directory of YAML files that Cookbook uses internally.
+Install
+=======
-The recipes themselves will generally be stored in per-recipe YAML
-files under 'recipe'.
+Download
+--------
-Send any bug reports to W. Trevor King <wking@drexel.edu>.
+Cookbook is published as a Git_ repository. See `cookbook's web
+interface`__ for more information.
+
+__ cookbook_
+
+Dependencies
+------------
+
+Outside of Django_ and the Python_ standard libraries, the only
+required dependency is `django-taggit`_ (docs__).
+
+__ dt2-docs_
+
+Quick-start
+===========
+
+If you don't have a Django project and you just want to run cookbook as
+a stand-alone service, you can use the example project written up in
+`example`. Set up the project (once)::
+
+ $ python example/manage.py syncdb
+
+See the `Django documentation`_ for more details.
+
+Run
+===
+
+Run the app on your local host (as many times as you like)::
+
+ $ python example/manage.py runserver
+
+You may need to add the current directory to `PYTHONPATH` so `python`
+can find the `cookbook` package. If you're running `bash`, that will
+look like
+
+ $ PYTHONPATH=".:$PYTHONPATH" python example/manage.py runserver
+
+Hacking
+=======
+
+This project was largely build following the `Django tutorial`_.
+That's a good place to start if you're new to Django.
+
+.. _Python: http://www.python.org/
+.. _Django: https://www.djangoproject.com/
+.. _Git: http://git-scm.com/
+.. _cookbook: http://physics.drexel.edu/~wking/code/git/gitweb.cgi?p=cookbook.git
+.. _django-taggit: https://github.com/alex/django-taggit
+.. _dt2-docs: http://django-taggit.readthedocs.org/en/latest/
+.. _Django documentation: https://docs.djangoproject.com/
+.. _Django tutorial: https://docs.djangoproject.com/en/1.3/intro/tutorial01/
+++ /dev/null
-#!/usr/bin/python
-
-# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
-#
-# This file is part of Cookbook.
-#
-# Cookbook is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or (at your
-# option) any later version.
-#
-# Cookbook is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Cookbook. If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import with_statement
-
-import os
-import os.path
-import uuid
-
-import cookbook
-from cookbook import Cookbook
-from cookbook.mom import MomParser
-from cookbook.cookbook import test as test_cookbook
-
-try:
- import jinja2
- import cherrypy
- from cookbook.server import Server
- from cookbook.server import test as test_server
-except ImportError, e:
- cherrypy = e
- def test_server():
- pass
-
-
-if __name__ == '__main__':
- import optparse
- import sys
-
- p = optparse.OptionParser()
- p.add_option('-t', '--test', dest='test', default=False,
- action='store_true', help='run internal tests and exit')
- p.add_option('-m', '--mom', dest='mom', metavar='PATH',
- help="load Mom's cookbook")
- p.add_option('-s', '--serve', dest='serve', default=False,
- action='store_true', help='serve cookbook')
- p.add_option('-a', '--address', dest='address', default='127.0.0.1',
- metavar='ADDR',
- help='address that the server will bind to (%default)')
- p.add_option('-p', '--port', dest='port', default='8080',
- metavar='PORT',
- help='port that the server will listen on (%default)')
-
- options,arguments = p.parse_args()
-
- if options.test == True:
- test_cookbook()
- test_server()
- sys.exit(0)
-
- if isinstance(cherrypy, ImportError):
- raise cherrypy
-
- sys.stderr.write('Loading cookbook\n')
- if options.mom == None:
- c = Cookbook()
- c.load()
- else:
- p = MomParser()
- c = p.parse(options.mom)
-
- if options.serve == False:
- sys.stderr.write('Saving cookbook\n')
- c.save('new-recipe')
- else:
- # HACK! to ensure we *always* get utf-8 output
- #reload(sys)
- #sys.setdefaultencoding('utf-8')
- sys.stderr.write('Serving cookbook\n')
- module_dir = os.path.dirname(os.path.abspath(cookbook.__file__))
- static_dir = os.path.join(module_dir, 'static')
- if not os.path.exists(static_dir):
- os.mkdir(static_dir)
- template_dir = os.path.join(module_dir, 'template')
- config = os.path.join(module_dir, 'config')
- s = Server(c, template_dir)
- if cherrypy.__version__.startswith('3.'):
- cherrypy.config.update({ # http://www.cherrypy.org/wiki/ConfigAPI
- 'server.socket_host': options.address,
- 'server.socket_port': int(options.port),
- 'tools.decode.on': True,
- 'tools.encode.on': True,
- 'tools.encode.encoding': 'utf8',
- 'tools.staticdir.root': static_dir,
- })
- if cherrypy.__version__.startswith('3.2'):
- get_ha1 = cherrypy.lib.auth_digest.get_ha1_file_htdigest(
- '.htaccess')
- digest_auth = {
- 'tools.auth_digest.on': True,
- 'tools.auth_digest.realm': 'cookbook',
- 'tools.auth_digest.get_ha1': get_ha1,
- 'tools.auth_digest.key': str(uuid.uuid4()),
- }
- else:
- passwds = {}
- with open('.htaccess', 'r') as f:
- for line in f:
- user,realm,ha1 = line.strip().split(':')
- passwds[user] = ha1 # use the ha1 as the password
- digest_auth = {
- 'tools.digest_auth.on': True,
- 'tools.digest_auth.realm': 'cookbook',
- 'tools.digest_auth.users': passwds,
- }
- print passwds
- del(passwds)
- app_config = {
- '/static': {
- 'tools.staticdir.on': True,
- 'tools.staticdir.dir': '',
- },
- '/edit': digest_auth,
- '/add_tag': digest_auth,
- '/remove_tag': digest_auth,
- }
- cherrypy.quickstart(root=s, config=app_config)
- elif cherrypy.__version__.startswith('2.'):
- cherrypy.root = s
- cherrypy.config.update({
- 'server.environment': 'production',
- 'server.socket_host': options.address,
- 'server.socket_port': int(options.port),
- 'decoding_filter.on': True,
- 'encoding_filter.on': True,
- 'encodinf_filter.encoding': 'utf8',
- 'static_filter.on': True,
- 'static_filter.dir': static_dir,
- })
- import pprint
- pprint.pprint(cherrypy.config.configs)
- cherrypy.server.start()
- s.cleanup()
-"""Cookbook package, for manipulating and serving cookbooks.
-
-Submodules:
-* cookbook, basic cookbook classes
-* server, for serving a cookbook over HTTP
-* mom, for parsing my mom's cookbook format
-"""
-
-from .cookbook import Cookbook
-
-__all__ = {'Cookbook':Cookbook}
+__version__ = '0.2'
--- /dev/null
+from django import forms
+from django.contrib import admin
+from django.db import models as django_models
+
+import models
+
+
+class IngredientInline (admin.TabularInline):
+ model = models.Ingredient
+ extra = 0
+
+
+class IngredientBlockAdmin (admin.ModelAdmin):
+ fieldsets = [
+ (None, {'fields': ['name']}),
+ ('Directions', {'fields': ['directions_markdown'],
+ 'classes': ['collapse']}),
+ ]
+ inlines = [IngredientInline]
+
+ list_display = ['name', 'recipe']
+ extra = 0
+
+
+class IngredientBlockInline (admin.TabularInline):
+ model = models.IngredientBlock
+ fieldsets = [
+ (None, {'fields': ['name']}),
+ ]
+ inlines = [IngredientInline]
+
+ list_display = ['name']
+ extra = 0
+ show_edit_link = True # https://code.djangoproject.com/ticket/13163
+ # work around 13163
+ #template = 'admin/edit_inline/tabular-13163.html'
+
+class RecipeAdmin (admin.ModelAdmin):
+ fieldsets = [
+ (None, {'fields': ['name']}),
+ ('Metadata', {'fields': ['author', 'source', 'url', 'x_yield', 'tags'],
+ 'classes': ['collapse']}),
+ ('Directions', {'fields': ['directions_markdown']}),
+ ]
+ inlines = [IngredientBlockInline]
+
+ list_display = ['name']
+
+
+admin.site.register(models.Recipe, RecipeAdmin)
+admin.site.register(models.IngredientBlock, IngredientBlockAdmin)
+admin.site.register(models.Amount)
+admin.site.register(models.Unit)
+admin.site.register(models.UnitSystem)
+admin.site.register(models.UnitType)
+++ /dev/null
-#!/usr/bin/python
-# -*- encoding: utf-8 -*-
-#
-# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
-#
-# This file is part of Cookbook.
-#
-# Cookbook is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or (at your
-# option) any later version.
-#
-# Cookbook is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Cookbook. If not, see <http://www.gnu.org/licenses/>.
-
-"""Represent a cookbook and recipes with Python classes.
-"""
-
-from __future__ import with_statement
-
-import os
-import os.path
-import textwrap
-import types
-from urllib import quote_plus
-
-import yaml
-
-
-def string_for_yaml(unicode_):
- """
- >>> string_for_yaml(None)
- >>> string_for_yaml('all ascii')
- 'all ascii'
- >>> string_for_yaml(u'all ascii')
- 'all ascii'
- >>> string_for_yaml(u'½ ascii')
- u'\\xc2\\xbd ascii'
- """
- if unicode_ == None:
- return unicode_
- try:
- string = unicode_.encode('ascii')
- return string
- except UnicodeEncodeError:
- return unicode_
-
-def to_yaml_object(obj):
- """
- >>> to_yaml_object(None)
- >>> to_yaml_object('all ascii')
- 'all ascii'
- >>> to_yaml_object(u'all ascii')
- 'all ascii'
- >>> to_yaml_object('all ascii')
- 'all ascii'
- >>> to_yaml_object(u'½ ascii')
- u'\\xc2\\xbd ascii'
- >>> class x (object):
- ... def to_yaml(self):
- ... return 'to_yaml return value'
- >>> to_yaml_object(x())
- 'to_yaml return value'
- >>> to_yaml_object([u'all ascii', u'½ ascii'])
- ['all ascii', u'\\xc2\\xbd ascii']
- """
- if obj == None:
- return obj
- if hasattr(obj, 'to_yaml'):
- return obj.to_yaml()
- if type(obj) in types.StringTypes:
- return string_for_yaml(obj)
- if hasattr(obj, '__len__'):
- ret = []
- for item in obj:
- ret.append(to_yaml_object(item))
- return ret
- raise NotImplementedError(
- 'cannot convert %s to YAMLable dict:\n%s' % (type(obj), unicode(obj)))
-
-
-class Amount (object):
- """
- >>> import pprint
- >>> a = Amount(value='1', units='T.')
- >>> str(a)
- '1 T.'
- >>> pprint.pprint(a.to_yaml())
- {'alternatives': [], 'range_': None, 'units': 'T.', 'value': '1'}
- >>> x = Amount()
- >>> x.from_yaml(a.to_yaml())
- >>> str(x)
- '1 T.'
- >>> b = Amount(value='1')
- >>> str(b)
- '1'
- >>> pprint.pprint(b.to_yaml())
- {'alternatives': [], 'range_': None, 'units': None, 'value': '1'}
- >>> x.from_yaml(b.to_yaml())
- >>> str(x)
- '1'
- >>> c = Amount(value='15', units='mL', alternatives=[a])
- >>> str(c)
- '15 mL (1 T.)'
- >>> pprint.pprint(c.to_yaml())
- {'alternatives': [{'alternatives': [],
- 'range_': None,
- 'units': 'T.',
- 'value': '1'}],
- 'range_': None,
- 'units': 'mL',
- 'value': '15'}
- >>> x.from_yaml(c.to_yaml())
- >>> str(x)
- '15 mL (1 T.)'
- >>> d = Amount(units='T.', range_=['1','2'])
- >>> str(d)
- '1-2 T.'
- >>> pprint.pprint(d.to_yaml())
- {'alternatives': [], 'range_': ['1', '2'], 'units': 'T.', 'value': None}
- >>> x.from_yaml(d.to_yaml())
- >>> str(x)
- '1-2 T.'
- """
- def __init__(self, value=None, units=None, range_=None, alternatives=None):
- self.value = value
- self.units = units
- self.range_ = range_
- if alternatives == None:
- alternatives = []
- self.alternatives = alternatives
-
- def __str__(self):
- return str(self.__unicode__())
-
- def __unicode__(self):
- if self.range_ == None:
- value = self.value
- else:
- value = '-'.join(self.range_)
- ret = [value]
- if self.units != None:
- ret.append(self.units)
- if len(self.alternatives) > 0:
- ret.append('(%s)' % ', '.join(
- [unicode(a) for a in self.alternatives]))
- return ' '.join([x for x in ret if x != None])
-
- def to_yaml(self):
- d = {}
- for key in ['value', 'range_', 'units', 'alternatives']:
- d[key] = to_yaml_object(getattr(self, key))
- return d
-
- def from_yaml(self, dict):
- for key in ['value', 'range_', 'units']:
- setattr(self, key, dict.get(key, None))
- self.alternatives = []
- for a in dict.get('alternatives', []):
- amount = Amount()
- amount.from_yaml(a)
- self.alternatives.append(amount)
-
-
-class Ingredient (object):
- """
- >>> import pprint
- >>> i = Ingredient('eye of newt', Amount('1'))
- >>> str(i)
- '1 eye of newt'
- >>> pprint.pprint(i.to_yaml())
- {'amount': {'alternatives': [], 'range_': None, 'units': None, 'value': '1'},
- 'name': 'eye of newt',
- 'note': None}
- >>> x = Ingredient()
- >>> x.from_yaml(i.to_yaml())
- >>> str(x)
- '1 eye of newt'
- >>> j = Ingredient('salamanders', Amount('2'), 'diced fine')
- >>> str(j)
- '2 salamanders, diced fine'
- >>> pprint.pprint(j.to_yaml())
- {'amount': {'alternatives': [], 'range_': None, 'units': None, 'value': '2'},
- 'name': 'salamanders',
- 'note': 'diced fine'}
- >>> x.from_yaml(j.to_yaml())
- >>> str(x)
- '2 salamanders, diced fine'
- """
- def __init__(self, name=None, amount=None, note=None):
- self.name = name
- self.amount = amount
- self.note = note
-
- def __str__(self):
- return str(self.__unicode__())
-
- def __unicode__(self):
- if self.note == None:
- return '%s %s' % (unicode(self.amount or ''), self.name)
- return '%s %s, %s' % (unicode(self.amount or ''), self.name, self.note)
-
- def to_yaml(self):
- d = {}
- for key in ['name', 'amount', 'note']:
- d[key] = to_yaml_object(getattr(self, key))
- return d
-
- def from_yaml(self, dict):
- for key in ['name', 'note']:
- setattr(self, key, dict.get(key, None))
- self.amount = Amount()
- self.amount.from_yaml(dict.get('amount', {}))
-
-
-class IngredientBlock (list):
- """
- >>> import pprint
- >>> ib = IngredientBlock(None, [
- ... Ingredient('eye of newt', Amount('1')),
- ... Ingredient('salamanders', Amount('2'), 'diced fine')])
- >>> print str(ib)
- Ingredients:
- 1 eye of newt
- 2 salamanders, diced fine
- >>> pprint.pprint(ib.to_yaml())
- {'ingredients': [{'amount': {'alternatives': [],
- 'range_': None,
- 'units': None,
- 'value': '1'},
- 'name': 'eye of newt',
- 'note': None},
- {'amount': {'alternatives': [],
- 'range_': None,
- 'units': None,
- 'value': '2'},
- 'name': 'salamanders',
- 'note': 'diced fine'}],
- 'name': None}
- >>> x = IngredientBlock()
- >>> x.from_yaml(ib.to_yaml())
- >>> print str(x)
- Ingredients:
- 1 eye of newt
- 2 salamanders, diced fine
- >>> ib.name = 'Dressing'
- >>> print str(ib)
- Dressing:
- 1 eye of newt
- 2 salamanders, diced fine
- >>> pprint.pprint(ib.to_yaml())
- {'ingredients': [{'amount': {'alternatives': [],
- 'range_': None,
- 'units': None,
- 'value': '1'},
- 'name': 'eye of newt',
- 'note': None},
- {'amount': {'alternatives': [],
- 'range_': None,
- 'units': None,
- 'value': '2'},
- 'name': 'salamanders',
- 'note': 'diced fine'}],
- 'name': 'Dressing'}
- >>> x = IngredientBlock()
- >>> x.from_yaml(ib.to_yaml())
- >>> print str(x)
- Dressing:
- 1 eye of newt
- 2 salamanders, diced fine
- """
- def __init__(self, name=None, *args, **kwargs):
- self.name = name
- super(IngredientBlock, self).__init__(*args, **kwargs)
-
- def heading(self):
- if self.name == None:
- return 'Ingredients'
- return self.name
-
- def __str__(self):
- return str(self.__unicode__())
-
- def __unicode__(self):
- ret = [unicode(i) for i in self]
- ret.insert(0, '%s:' % self.heading())
- return '\n '.join(ret)
-
- def to_yaml(self):
- d = {}
- for key in ['name']:
- d[key] = to_yaml_object(getattr(self, key))
- d['ingredients'] = to_yaml_object(list(self))
- return d
-
- def from_yaml(self, dict):
- self.name = dict.get('name', None)
- while len(self) > 0:
- self.pop()
- for i in dict.get('ingredients', []):
- ingredient = Ingredient()
- ingredient.from_yaml(i)
- self.append(ingredient)
-
-
-class Directions (list):
- """
- >>> import pprint
- >>> d = Directions(['paragraph 1', 'paragraph 2', 'paragraph 3'])
- >>> print str(d)
- paragraph 1
- <BLANKLINE>
- paragraph 2
- <BLANKLINE>
- paragraph 3
- >>> pprint.pprint(d.to_yaml())
- 'paragraph 1\\n\\nparagraph 2\\n\\nparagraph 3'
- >>> x = Directions()
- >>> x.from_yaml(d.to_yaml())
- >>> print str(x)
- paragraph 1
- <BLANKLINE>
- paragraph 2
- <BLANKLINE>
- paragraph 3
- >>> pprint.pprint(x.to_yaml())
- 'paragraph 1\\n\\nparagraph 2\\n\\nparagraph 3'
- """
- def __str__(self):
- return str(self.__unicode__())
-
- def __unicode__(self):
- return '\n\n'.join(self)
-
- def wrapped_paragraphs(self, *args, **kwargs):
- return ['\n'.join(
- textwrap.wrap(
- paragraph,
- *args,
- **kwargs))
- for paragraph in self]
-
- def wrap(self, *args, **kwargs):
- return '\n\n'.join(self.wrapped_paragraphs(*args, **kwargs))
-
- def to_yaml(self):
- return string_for_yaml('\n\n'.join([paragraph.rstrip('\n')
- for paragraph in self]))
-
- def from_yaml(self, string):
- if string == None:
- return
- while len(self) > 0:
- self.pop()
- for paragraph in string.split('\n\n'):
- self.append(paragraph)
-
-
-class Recipe (object):
- """
- >>> import pprint
- >>> r = Recipe('Hot Newt Dressing',
- ... [IngredientBlock(None, [
- ... Ingredient('eye of newt', Amount('1')),
- ... Ingredient('salamanders', Amount('2'), 'diced fine')])],
- ... Directions(['Mix ingredients until salamander starts to smoke',
- ... 'Serve warm over rice']),
- ... yield_='enough for one apprentice',
- ... author='Merlin',
- ... source='CENSORED',
- ... url='http://merlin.uk/recipes/1234/',
- ... tags=['dinner', 'easy', 'apprentice', 'eye', 'newt', 'salamander'])
- >>> print str(r)
- Recipe: Hot Newt Dressing
- Yield: enough for one apprentice
- From: Merlin
- Source: CENSORED
- URL: http://merlin.uk/recipes/1234/
- Ingredients:
- 1 eye of newt
- 2 salamanders, diced fine
- Mix ingredients until salamander starts to smoke
- <BLANKLINE>
- Serve warm over rice
- >>> r.matches_tags(None)
- True
- >>> r.matches_tags([])
- True
- >>> r.matches_tags(['dinner', 'apprentice'])
- True
- >>> r.matches_tags(['dinner', 'dragon'])
- False
- >>> pprint.pprint(r.to_yaml())
- {'author': 'Merlin',
- 'directions': 'Mix ingredients until salamander starts to smoke\\n\\nServe warm over rice',
- 'ingredient_blocks': [{'ingredients': [{'amount': {'alternatives': [],
- 'range_': None,
- 'units': None,
- 'value': '1'},
- 'name': 'eye of newt',
- 'note': None},
- {'amount': {'alternatives': [],
- 'range_': None,
- 'units': None,
- 'value': '2'},
- 'name': 'salamanders',
- 'note': 'diced fine'}],
- 'name': None}],
- 'name': 'Hot Newt Dressing',
- 'source': 'CENSORED',
- 'tags': ['dinner', 'easy', 'apprentice', 'eye', 'newt', 'salamander'],
- 'url': 'http://merlin.uk/recipes/1234/',
- 'yield_': 'enough for one apprentice'}
- >>> x = Recipe()
- >>> x.from_yaml(r.to_yaml())
- >>> print str(x)
- Recipe: Hot Newt Dressing
- Yield: enough for one apprentice
- From: Merlin
- Source: CENSORED
- URL: http://merlin.uk/recipes/1234/
- Ingredients:
- 1 eye of newt
- 2 salamanders, diced fine
- Mix ingredients until salamander starts to smoke
- <BLANKLINE>
- Serve warm over rice
- """
- def __init__(self, name=None, ingredient_blocks=None, directions=None,
- yield_=None, author=None, source=None, url=None, tags=None):
- self.name = name
- self.ingredient_blocks = ingredient_blocks
- self.directions = directions
- self.yield_ = yield_
- self.author = author
- self.source = source
- self.url = url
- self.tags = tags
- self.path = None
-
- def clean_name(self, ascii=False):
- name = self.name
- for from_,to in [(' ','_'), ('/', '_'), (',', ''), ('&', 'and')]:
- name = name.replace(from_, to)
- if ascii == True:
- return quote_plus(name.encode('utf-8'))
- return name
-
- def matches_tags(self, tags):
- """Return True if this recipe is tagges with each of the tags in tags.
- """
- if tags in [None, []]:
- return True
- if self.tags == None:
- return False
- for t in tags:
- if t not in self.tags:
- return False
- return True
-
- def __str__(self):
- return str(self.__unicode__())
-
- def __unicode__(self):
- return '\n'.join([
- 'Recipe: %s' % self.name,
- 'Yield: %s' % self.yield_,
- 'From: %s' % self.author,
- 'Source: %s' % self.source,
- 'URL: %s' % self.url,
- '\n'.join([unicode(ib) for ib in self.ingredient_blocks]),
- unicode(self.directions),
- ])
-
- def to_yaml(self):
- d = {}
- for key in ['name', 'ingredient_blocks', 'directions', 'yield_',
- 'author', 'source', 'url', 'tags']:
- d[key] = to_yaml_object(getattr(self, key))
- return d
-
- def from_yaml(self, dict):
- for key in ['name', 'yield_', 'author', 'source', 'url', 'tags']:
- setattr(self, key, dict.get(key, None))
- self.ingredient_blocks = []
- for ib in dict.get('ingredient_blocks', []):
- ingredient_block = IngredientBlock()
- ingredient_block.from_yaml(ib)
- self.ingredient_blocks.append(ingredient_block)
- self.directions = Directions()
- self.directions.from_yaml(dict.get('directions', None))
-
- def save(self, stream, path=None):
- yaml.dump(self.to_yaml(), stream,
- default_flow_style=False, allow_unicode=True, width=78)
- if path != None:
- self.path = path
-
- def load(self, stream, path=None):
- dict = yaml.load(stream)
- self.from_yaml(dict)
- if path != None:
- self.path = path
-
-
-class Cookbook (list):
- """
- TODO: doctests
- """
- def __init__(self, name="Mom's cookbook", *args, **kwargs):
- self.name = name
- self.save_dir = u'recipe'
- super(Cookbook, self).__init__(*args, **kwargs)
-
- def save(self):
- if not os.path.isdir(self.save_dir):
- os.mkdir(self.save_dir)
- paths = []
- for recipe in self:
- p = self._free_path(recipe, paths)
- paths.append(p)
- with open(p, 'w') as f:
- recipe.save(f, p)
-
- def load(self):
- for path in sorted(os.listdir(self.save_dir)):
- r = Recipe()
- p = os.path.join(self.save_dir, path)
- with open(p, 'r') as f:
- r.load(f, p)
- self.append(r)
-
- def make_index(self):
- self.index = {}
- paths = [recipe.path for recipe in self]
- for recipe in self:
- self.index[recipe.name] = recipe
- self.index[recipe.clean_name()] = recipe
- self.index[recipe.clean_name(ascii=True)] = recipe
- if recipe.path == None:
- p = self._free_path(recipe, paths)
- paths.append(p)
- recipe.path = p
-
- def _free_path(self, recipe, paths):
- base_path = os.path.join(self.save_dir, recipe.clean_name())
- path = base_path
- i = 2
- while path in paths:
- path = '%s_%d' % (base_path, i)
- i += 1
- return path
-
- def tags(self):
- """List all tags used in this cookbook.
- """
- tags = set()
- for recipe in self:
- if recipe.tags != None:
- tags = tags.union(set(recipe.tags))
- return sorted(tags)
-
- def tagged(self, tags=None):
- """Iterate through all recipes matching the given list of tags.
- """
- for recipe in self:
- if recipe.matches_tags(tags):
- yield recipe
-
-
-def test():
- import doctest
- doctest.testmod()
--- /dev/null
+from django.db import models
+import markdown
+from taggit.managers import TaggableManager
+
+
+class UnitType (models.Model):
+ "Weight, length, count, time, etc."
+ name = models.CharField(max_length=40)
+
+ def __unicode__(self):
+ return u'{0.name}'.format(self)
+
+class UnitSystem (models.Model):
+ "SI, CGS, British Imperial, US, etc."
+ name = models.CharField(max_length=40)
+
+ def __unicode__(self):
+ return u'{0.name}'.format(self)
+
+class Unit (models.Model):
+ "Kilograms, pounds, liters, gallons, etc."
+ abbrev = models.CharField('abbreviation', max_length=6)
+ name = models.CharField(max_length=40)
+ type = models.ForeignKey(UnitType)
+ system = models.ForeignKey(UnitSystem)
+ si_scale = models.DecimalField(max_digits=30, decimal_places=15)
+ si_offset = models.DecimalField(max_digits=30, decimal_places=15)
+
+ def __unicode__(self):
+ return u'{0.abbrev}'.format(self)
+
+class Amount (models.Model):
+ "1 kg, 2-3 lb., 0.5 (0.3-0.6) gal., etc."
+ unit = models.ForeignKey(Unit)
+ value = models.DecimalField(max_digits=10, decimal_places=4)
+ min_value = models.DecimalField(
+ 'minimum value', max_digits=10, decimal_places=4,
+ null=True, blank=True)
+ max_value = models.DecimalField(
+ 'maximum value', max_digits=10, decimal_places=4,
+ null=True, blank=True)
+
+ def __unicode__(self):
+ if self.min_value is None and self.max_value is None:
+ value = self.value
+ elif self.min_value is None:
+ value = '{0.value}-{0.max_value}'.format(self)
+ elif self.max_value is None:
+ value = '{0.min_value}-{0.value}'.format(self)
+ else:
+ value = '{0.value} ({0.min_value}-{0.max_value})'.format(self)
+ return u'{0} {1.unit}'.format(value, self)
+
+class Recipe (models.Model):
+ name = models.CharField(max_length=200)
+ directions_markdown = models.TextField(
+ 'directions', help_text='Markdown syntax')
+ directions = models.TextField('directions as HTML', blank=True, null=True)
+ # yield is a reserved word
+ x_yield = models.OneToOneField(
+ Amount, verbose_name='yield', db_column='yield', null=True, blank=True)
+ author = models.CharField(max_length=200, null=True, blank=True)
+ source = models.CharField(max_length=200, null=True, blank=True)
+ url = models.URLField('URL', null=True, blank=True)
+ tags = TaggableManager(blank=True)
+
+ class Meta:
+ ordering = ['name']
+
+ def __unicode__(self):
+ return u'{0.name}'.format(self)
+
+ def save(self):
+ # https://code.djangoproject.com/wiki/UsingMarkup
+ self.directions = markdown.markdown(self.directions_markdown)
+ super(Recipe, self).save()
+
+
+class IngredientBlock (models.Model):
+ name = models.CharField(max_length=200)
+ directions_markdown = models.TextField(
+ 'directions', help_text='markdown syntax', blank=True, null=True)
+ directions = models.TextField('directions as HTML', blank=True, null=True)
+ recipe = models.ForeignKey(Recipe)
+
+ class Meta:
+ ordering = ['recipe', 'name']
+
+ def __unicode__(self):
+ return u'{0.name}'.format(self)
+
+ def save(self):
+ if self.directions_markdown:
+ self.directions = markdown.markdown(self.directions_markdown)
+ super(IngredientBlock, self).save()
+
+
+class Ingredient (models.Model):
+ "1 kg, 2 lb., 3.4 L, 0.5 gal., etc."
+ amount = models.OneToOneField(Amount, null=True, blank=True)
+ name = models.CharField(max_length=200)
+ note = models.CharField(max_length=200, null=True, blank=True)
+ block = models.ForeignKey(IngredientBlock)
+
+ def __unicode__(self):
+ fmt = '{0.name}'
+ if self.amount:
+ fmt = '{0.amount} ' + fmt
+ if self.note:
+ fmt += u', {0.note}'
+ return fmt.format(self)
+++ /dev/null
-# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
-#
-# This file is part of Cookbook.
-#
-# Cookbook is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or (at your
-# option) any later version.
-#
-# Cookbook is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Cookbook. If not, see <http://www.gnu.org/licenses/>.
-
-"""Hack parser for standardizing my Mom's cookbook text.
-"""
-
-from .cookbook import (
- Cookbook, Recipe, IngredientBlock, Ingredient, Amount, Directions)
-
-
-class MomParser (object):
- def parse(self, filename):
- c = Cookbook()
- consecutive_blanks = 100
- recipe_lines = None
- for line in file(filename, 'r'):
- line = line.strip().decode('utf-8')
- if line == '':
- if recipe_lines != None and consecutive_blanks == 0:
- recipe_lines.append('')
- consecutive_blanks += 1
- continue
- if consecutive_blanks >= 2:
- if recipe_lines != None:
- c.append(self._parse_recipe(recipe_lines))
- recipe_lines = [line]
- else:
- recipe_lines.append(line)
- consecutive_blanks = 0
- return c
-
- def _parse_recipe(self, lines):
- name = lines.pop(0)
- yield_,author,source,url,lines = self._parse_yield_line(lines)
- ingredient_blocks,lines = self._parse_ingredient_blocks(lines)
- directions,lines = self._parse_directions(lines)
- assert len(lines) == 0, lines
- return Recipe(
- name=name,
- ingredient_blocks=ingredient_blocks,
- directions=directions,
- yield_=yield_,
- author=author,
- source=source,
- url=url)
-
- def _parse_yield_line(self, lines):
- while len(lines) > 0 and lines[0] == '':
- lines.pop(0)
- fields = ['yield', 'from', 'source', 'url']
- yield_ = author = source = url = None
- matching_line = False
- for field in fields:
- if field in lines[0].lower():
- matching_line = True
- break
- if matching_line == True:
- bits = lines.pop(0).split('\t')
- for bit in bits:
- for field in fields:
- if bit.lower().startswith(field+':'):
- value = bit[len(field+':'):].strip()
- if field == 'yield':
- yield_ = value.replace('Serving', 'serving')
- elif field == 'from':
- author = value
- elif field == 'source':
- source = value
- elif field == 'url':
- url = value
- break
- return (yield_, author, source, url, lines)
-
- def _parse_ingredient_blocks(self, lines):
- ingredient_blocks = []
- first_block = True
- while True:
- while len(lines) > 0 and lines[0] == '': # scroll past blanks
- lines.pop(0)
- if (len(lines) == 0
- or not (first_block == True
- or lines[0].endswith(':'))):
- break
- if lines[0].endswith(':'):
- line = lines.pop(0)
- name = line[:-1].strip()
- else:
- name = None
- block = IngredientBlock(name)
- while len(lines) > 0 and lines[0] != '':
- block.append(self._parse_ingredient_line(lines.pop(0)))
- ingredient_blocks.append(block)
- first_block = False
- return (ingredient_blocks, lines)
-
- def _parse_ingredient_line(self, line):
- if line.lower().startswith('1 red'):
- line = '1 # red'+line[len('1 red'):]
- try:
- value,units,name = line.split(' ', 2)
- except ValueError:
- print line,
- raise
- if units == '#':
- units = None
- elif units == 'Large':
- units = 'large'
- elif units == 'Cloves':
- units = 'cloves'
- return Ingredient(name, Amount(value, units))
-
- def _parse_directions(self, lines):
- directions = Directions()
- paragraph = []
- for line in lines:
- if line == '':
- if len(paragraph) > 0:
- directions.append('\n'.join(paragraph))
- paragraph = []
- else:
- paragraph.append(line)
- return (directions, [])
+++ /dev/null
-# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
-#
-# This file is part of Cookbook.
-#
-# Cookbook is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or (at your
-# option) any later version.
-#
-# Cookbook is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Cookbook. If not, see <http://www.gnu.org/licenses/>.
-
-"""Serve cookbooks over HTTP.
-"""
-
-from __future__ import absolute_import
-from __future__ import with_statement
-
-import os
-import random
-import re
-import types
-from urllib import urlencode
-import uuid
-from xml.sax import saxutils
-
-import cherrypy
-from jinja2 import Environment, FileSystemLoader
-
-from .cookbook import Recipe, Directions, IngredientBlock, Ingredient, Amount
-
-
-class Server (object):
- """Cookbook web interface."""
-
- def __init__(self, cookbook, template_root):
- self.cookbook = cookbook
- self.cookbook.make_index()
- self.env = Environment(loader=FileSystemLoader(template_root))
- # name regular expressions
- self._action_ingredient_block_regexp = re.compile(
- 'ingredient block ([0-9]*) ([a-z ]*)')
- self._action_ingredient_regexp = re.compile(
- 'ingredient ([0-9]*) ([0-9]*) ([a-z ]*)')
- # value regular expressions
- self._tag_regexp = re.compile('[a-zA-Z./ ].*') # allowed characters
- self._action_add_ingredient_block_regexp = re.compile(
- 'add ingredient block')
- self._action_remove_ingredient_block_regexp = re.compile(
- 'remove ingredient block ([0-9]*)')
- self._action_add_ingredient_regexp = re.compile(
- 'add ingredient ([0-9]*)')
- self._action_remove_ingredient_regexp = re.compile(
- 'remove ingredient ([0-9]*) ([0-9]*)')
-
- def cleanup(self):
- #self.cookbook.save('new-recipe')
- pass
-
- @cherrypy.expose
- def index(self, tag=None):
- """Recipe index page.
-
- Recipes can be filtered by tag.
- """
- if isinstance(tag, types.StringTypes):
- tag = [tag]
- template = self.env.get_template('recipes.html')
- return template.render(cookbook=self.cookbook,
- recipes=list(self.cookbook.tagged(tag)),
- selected_tags=tag)
-
- @cherrypy.expose
- def recipe(self, name=None):
- """Single recipe page.
- """
- if name == None:
- recipe = random.choice(self.cookbook)
- else:
- recipe = self.cookbook.index[name]
- tag_links = [
- '<a href="./?%s">%s</a>' % (urlencode({'tag':t}), t)
- for t in (recipe.tags or [])]
- template = self.env.get_template('recipe.html')
- return template.render(cookbook=self.cookbook, recipe=recipe,
- tag_links=tag_links)
-
- @cherrypy.expose
- def add_tag(self, name, tag):
- """Add a tag to a single recipe."""
- recipe = self.cookbook.index[name]
- if recipe.tags == None:
- recipe.tags = []
- tag = self._clean_tag(tag)
- if tag != None and tag not in recipe.tags:
- recipe.tags.append(tag)
- with open(recipe.path, 'w') as f:
- recipe.save(f)
- raise cherrypy.HTTPRedirect(
- u'recipe?name=%s' % recipe.clean_name(ascii=True), status=302)
-
- @cherrypy.expose
- def remove_tag(self, name, tag):
- """Remove a tag from a single recipe."""
- recipe = self.cookbook.index[name]
- if recipe.tags == None:
- return
- tag = self._clean_tag(tag)
- if tag != None and tag in recipe.tags:
- recipe.tags.remove(tag)
- with open(recipe.path, 'w') as f:
- recipe.save(f)
- raise cherrypy.HTTPRedirect(
- u'recipe?name=%s' % recipe.clean_name(ascii=True), status=302)
-
- def _clean_tag(self, tag):
- """Sanitize tag."""
- if not isinstance(tag, types.StringTypes):
- if len(tag) == 2 and '' in tag:
- # User used either dropdown or textbox
- tag.remove('')
- tag = tag[0]
- else:
- # User used both dropdown and textbox
- return None
- m = self._tag_regexp.match(tag)
- if m != None:
- return m.group()
- return None
-
- @cherrypy.expose
- def edit(self, name=None, **kwargs):
- """Remove a tag from a single recipe."""
- name,recipe,action = self._normalize_edit_params(name, **kwargs)
- if action == 'remove':
- recipe = self.cookbook.index[name]
- os.remove(recipe.path)
- self.cookbook.remove(recipe)
- self.cookbook.make_index()
- raise cherrypy.HTTPRedirect(u'.', status=302)
- elif action.startswith('edit'):
- self._update_recipe(name, recipe)
- if action == 'edit and redirect': # done editing this recipe
- raise cherrypy.HTTPRedirect(
- u'recipe?name=%s' % recipe.clean_name(ascii=True),
- status=302)
- elif recipe.name != name:
- raise cherrypy.HTTPRedirect(
- u'edit?name=%s' % recipe.clean_name(ascii=True),
- status=302)
- template = self.env.get_template('edit-recipe.html')
- return template.render(cookbook=self.cookbook,
- recipe=recipe,
- form='\n'.join(self._recipe_form(recipe)))
-
- def _update_recipe(self, name, recipe):
- if len(recipe.ingredient_blocks) == 0:
- recipe.ingredient_blocks = None
- if name not in self.cookbook.index: # new recipe
- print 'new'
- self.cookbook.append(recipe)
- self.cookbook.make_index()
- self.cookbook.sort(key=lambda r: r.path)
- elif name != recipe.name: # renamed recipe
- print 'rename'
- os.remove(recipe.path)
- recipe.path = None
- self.cookbook.make_index()
- with open(recipe.path, 'w') as f:
- recipe.save(f)
-
- def _normalize_edit_params(self, name, **kwargs):
- action = 'view form'
- if name in self.cookbook.index:
- recipe = self.cookbook.index[name]
- name = recipe.name # get the canonical name for this recipe
- else:
- # Use uuid4() to generate a random name if name is None
- recipe = Recipe(name=(name or str(uuid.uuid4())))
- recipe.path = None
- # non-ingredient text updates
- for attr in ['name','directions','yield_','author','source','url']:
- n = self._recipe_attr_to_name(attr)
- if n not in kwargs:
- continue
- v = kwargs.get(n).strip()
- if v == '':
- v = None
- elif attr == 'directions':
- d = Directions()
- v = v.replace('\r\n', '\n').replace('\r', '\n')
- for p in v.split('\n\n'):
- p = p.strip()
- if len(p) > 0:
- d.append(p)
- v = d
- if v != getattr(recipe, attr):
- setattr(recipe, attr, v)
- action = 'edit'
- if recipe.ingredient_blocks == None:
- recipe.ingredient_blocks = []
- # protect against index-changing operations
- ingredient_block_map = dict(
- [(i,i) for i in range(len(recipe.ingredient_blocks))])
- ingredient_map = dict(
- [(i, dict([(j,j) for j in range(len(ib))]))
- for i,ib in enumerate(recipe.ingredient_blocks)])
- # ingredient text updates
- for key,value in kwargs.items():
- for k in ['ingredient_block', 'ingredient']:
- regexp = getattr(self, '_action_%s_regexp' % k)
- m = regexp.match(key)
- if m != None:
- handler = getattr(self, '_action_%s' % k)
- kws = {'ingredient_block_map': ingredient_block_map,
- 'ingredient_map': ingredient_map}
- action = handler(recipe, action, value, *m.groups(), **kws)
- break
- # button updates
- action = kwargs.get('action', action)
- if action == 'submit':
- action = 'edit and redirect'
- for a in ['add_ingredient_block', 'remove_ingredient_block',
- 'add_ingredient', 'remove_ingredient']:
- regexp = getattr(self, '_action_%s_regexp' % a)
- m = regexp.match(action)
- if m != None:
- handler = getattr(self, '_action_%s' % a)
- kws = {'ingredient_block_map': ingredient_block_map,
- 'ingredient_map': ingredient_map}
- action = handler(recipe, action, *m.groups(), **kws)
- break
- return (name, recipe, action)
-
- def _action_ingredient_block(self, recipe, action, value,
- block_index, block_action,
- ingredient_block_map, **kwargs):
- block_index = ingredient_block_map[int(block_index)]
- ib = recipe.ingredient_blocks[block_index]
- if value == '':
- value = None
- if block_action == 'position' and value not in [None, '']:
- value = int(value)
- if value != block_index:
- ibs = recipe.ingredient_blocks
- ibs.insert(value, ibs.pop(block_index))
- for i in range(min(block_index, value),
- max(block_index, value)+1):
- ingredient_block_map[i] = i+1
- ingredient_block_map[block_index] = value
- return 'edit'
- elif block_action == 'name':
- if value != ib.name:
- ib.name = value
- return 'edit'
- return action
-
- def _action_ingredient(self, recipe, action, value,
- block_index, index, ingredient_action,
- ingredient_block_map, ingredient_map, **kwargs):
- block_index = ingredient_block_map[int(block_index)]
- index = ingredient_map[block_index][int(index)]
- ingredient = recipe.ingredient_blocks[block_index][index]
- if value == '':
- value = None
- if ingredient_action == 'position' and value != None:
- value = int(value)
- if value != block_index:
- ibs = recipe.ingredient_blocks
- ibs.insert(value, ibs.pop(block_index))
- for i in range(min(block_index, value),
- max(block_index, value)+1):
- ingredient_block_map[i] = i+1
- ingredient_block_map[block_index] = value
- return 'edit'
- elif ingredient_action in ['name', 'note']:
- if value != getattr(ingredient, ingredient_action):
- setattr(ingredient, ingredient_action, value)
- return 'edit'
- elif ingredient_action in ['value', 'units']:
- if value != getattr(ingredient.amount, ingredient_action):
- setattr(ingredient.amount, ingredient_action, value)
- return 'edit'
- return action
-
- def _action_add_ingredient_block(self, recipe, action,
- ingredient_block_map,
- ingredient_map, **kwargs):
- recipe.ingredient_blocks.append(IngredientBlock())
- block_index = len(recipe.ingredient_blocks)-1
- ingredient_block_map[block_index] = block_index
- ingredient_map[block_index] = {}
- return 'edit'
-
- def _action_remove_ingredient_block(self, recipe, action, block_index,
- ingredient_block_map, **kwargs):
- block_index = ingredient_block_map[int(block_index)]
- recipe.ingredient_blocks.pop(block_index)
- for k,v in ingredient_block_map.items():
- if v >= block_index:
- ingredient_block_map[k] = v-1
- return 'edit'
-
- def _action_add_ingredient(self, recipe, action, block_index,
- ingredient_block_map,
- ingredient_map, **kwargs):
- block_index = ingredient_block_map[int(block_index)]
- recipe.ingredient_blocks[block_index].append(Ingredient(
- amount=Amount()))
- index = len(recipe.ingredient_blocks[block_index])-1
- ingredient_map[block_index][index] = index
- return 'edit'
-
- def _action_remove_ingredient(self, recipe, action, block_index, index,
- ingredient_block_map,
- ingredient_map, **kwargs):
- block_index = ingredient_block_map[int(block_index)]
- index = ingredient_map[block_index][int(index)]
- recipe.ingredient_blocks[block_index].pop(index)
- for k,v in ingredient_map[block_index].items():
- if v >= index:
- ingredient_map[block_index][k] = v-1
- return 'edit'
-
- def _recipe_form(self, recipe):
- lines = [
- '<form action="" method="post">',
- '<table>',
- ]
- for attr in ['name', 'yield_', 'author', 'source', 'url']:
- lines.extend(self._text_form(
- self._recipe_attr_to_name(attr),
- self._recipe_attr_to_user(attr),
- getattr(recipe, attr)))
- for i,ib in enumerate(recipe.ingredient_blocks):
- lines.extend(self._ingredient_block_form(ib, i))
- lines.extend(self._submit_form('add ingredient block'))
- lines.extend(self._textarea_form(
- self._recipe_attr_to_name('directions'),
- self._recipe_attr_to_user('directions'),
- '\n\n'.join(recipe.directions or Directions())))
- lines.extend(self._submit_form('submit'))
- lines.extend([
- '</table>',
- '</form>',
- ])
- return lines
-
- def _ingredient_block_form(self, ingredient_block, index):
- lines = self._submit_form(
- 'remove ingredient block %d' % index,
- note='Ingredient block %d' % index)
- for name,user,value in [
- ('ingredient block %d position' % index,
- 'Position (%d)' % index,
- None),
- ('ingredient block %d name' % index,
- 'Name',
- ingredient_block.name),
- ]:
- lines.extend(self._text_form(name, user, value))
- for i,ingredient in enumerate(ingredient_block):
- lines.extend(self._ingredient_form(ingredient, index, i))
- lines.extend(self._submit_form('add ingredient %d' % index))
- return lines
-
- def _ingredient_form(self, ingredient, block_index, index):
- lines = self._submit_form(
- 'remove ingredient %d %d' % (block_index, index),
- note='Ingredient %d %d' % (block_index, index))
- for name,user,value in [
- ('ingredient %d %d position' % (block_index, index),
- 'Position (%d)' % index,
- ''),
- ('ingredient %d %d value' % (block_index, index),
- 'Value',
- ingredient.amount.value),
- ('ingredient %d %d units' % (block_index, index),
- 'Units',
- ingredient.amount.units),
- ('ingredient %d %d name' % (block_index, index),
- 'Name',
- ingredient.name),
- ('ingredient %d %d note' % (block_index, index),
- 'Note',
- ingredient.note),
- ]:
- lines.extend(self._text_form(name, user, value))
- # TODO: Amount: range_, alternatives
- return lines
-
- def _submit_form(self, value, note=''):
- return [
- ' <tr>',
- ' <td>%s</td>' % note,
- ' <td><input type="submit" name="action" value="%s"/></td>'
- % value,
- ' </tr>',
- ]
-
- def _text_form(self, name, user, value):
- return [
- ' <tr>',
- ' <td><label for="%s">%s</label></td>' % (name, user),
- ' <td><input type="text" name="%s" size="60" value="%s"/></td>'
- % (name, value or ''),
- ' </tr>',
- ]
-
- def _textarea_form(self, name, user, value):
- return [
- ' <tr>',
- ' <td><label for="%s">%s</label></td>' % (name, user),
- ' <td><textarea name="%s" rows="20" cols="60"/>' % name,
- value,
- ' </textarea></td>'
- ' </tr>',
- ]
-
- def _recipe_attr_to_name(self, attr):
- if attr == 'yield_':
- return 'yield'
- elif attr == 'name':
- return 'new_name'
- return attr
-
- def _recipe_attr_to_user(self, attr):
- if attr == 'yield_':
- attr = 'yield'
- return attr.capitalize()
-
-
-def test():
- import doctest
- doctest.testmod()
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-<html>
- <head>
- <title>{{ cookbook.name }}</title>
-<!--
- <link rel="stylesheet" type="text/css" media="screen"
- href="/static/style.css"/>
--->
- </head>
-
- <body>
- <div id="main-pane">
- <div id="header" class="inside-main-pane">
- <h1><a href="./">{{ cookbook.name }}</a></h1>
- </div>
- <div id="content-pane" class="inside-main-pane">
- <h1>{% block page_title %}{% endblock %}</h1>
- {% block content %}{% endblock %}
- </div>
- <div id="footer" class="inside-main-pane">
- <p>
- Powered by
- <a href="http://www.physics.drexel.edu/">Trevor King's</a>
- <a href="http://www.physics.drexel.edu/~wking/code/git/git.php?p=cookbook.git">Cookbook</a>.
- Built using <a href="http://cherrypy.org/">CherryPy</a>
- and <a href="http://jinja.pocoo.org/2/">Jinja2</a>.
- </p>
- </div>
- </div>
- </body>
-</html>
+++ /dev/null
-{% extends "base.html" %}
-
-{% block page_title %}
- {% if recipe.url %}
- <a href="{{ recipe.url }}">{{ recipe.name }}</a>
- {% else %}
- {{ recipe.name }}
- {% endif %}
-{% endblock %}
-
-{% block content %}
- {{ form }}
-{% endblock %}
+++ /dev/null
-{% extends "base.html" %}
-
-{% block page_title %}
- {% if recipe.url %}
- <a href="{{ recipe.url }}">{{ recipe.name }}</a>
- {% else %}
- {{ recipe.name }}
- {% endif %}
-{% endblock %}
-
-{% block content %}
- <p id="recipe-details">
- {% if recipe.yield_ %}
- <span class="detail-field-header">Yield:</span>
- <span class="detail-field-contents">{{ recipe.yield_ }}</span><br/>
- {% endif %}
- {% if recipe.author %}
- <span class="detail-field-header">Author:</span>
- <span class="detail-field-contents">{{ recipe.author }}</span><br/>
- {% endif %}
- {% if recipe.source %}
- <span class="detail-field-header">Source:</span>
- <span class="detail-field-contents">{{ recipe.source }}</span><br/>
- {% endif %}
- {% if recipe.tags %}
- <span class="detail-field-header">Tags:</span>
- <span class="detail-field-contents">{{ ', '.join(tag_links) }}
- </span><br/>
- {% endif %}
- </p>
- <form action="add_tag" method="get">
- <input type="hidden" name="name" value="{{ recipe.name }}"/>
- <select name="tag">
- <option value="">textbox</option>
- {% for tag in cookbook.tags() %}
- <option value="{{ tag }}">{{ tag }}</option>
- {% endfor %}
- </select>
- <input type="text" name="tag" value=""/>
- <input type="submit" value="Add tag"/>
- </form>
- {% if recipe.tags %}
- <form action="remove_tag" method="get">
- <input type="hidden" name="name" value="{{ recipe.name }}"/>
- <select name="tag">
- {% for tag in recipe.tags %}
- <option value="{{ tag }}">{{ tag }}</option>
- {% endfor %}
- </select>
- <input type="submit" value="Remove tag"/>
- </form>
- {% endif %}
-
- {% if recipe.ingredient_blocks %}
- <div id="recipe-ingredient-blocks">
- {% for ingredient_block in recipe.ingredient_blocks %}
- <h2>{{ ingredient_block.heading() }}</h2>
- <table>
- {% for ingredient in ingredient_block %}
- <tr><td>{{ ingredient.__unicode__() }}</td></tr>
- {% endfor %}
- </table>
- {% endfor %}
- </div>
- {% endif %}
-
- {% if recipe.directions %}
- <div id="recipe-directions">
- <h2>Directions</h2>
- {% for paragraph in recipe.directions.wrapped_paragraphs() %}
- <p>{{ paragraph }}</p>
- {% endfor %}
- </div>
- {% endif %}
-
- <ul>
- <li><a href="edit?name={{ recipe.clean_name(ascii=True) }}">
- edit receipe</a></li>
- <li><a href="edit?action=remove&name={{ recipe.clean_name(ascii=True) }}">
- remove receipe</a></li>
- </ul>
-{% endblock %}
+++ /dev/null
-{% extends "base.html" %}
-
-{% block page_title %}
- Index {% if selected_tags %}({{ ', '.join(selected_tags) }}){% endif %}
-{% endblock %}
-
-{% block content %}
- <form action="" method="get" style="float:right;">
- <select name="tag" size="5" multiple="multiple">
- {% for tag in cookbook.tags() %}
- <option value="{{ tag }}">{{ tag }}</option>
- {% endfor %}
- </select><br/>
- <input type="submit" value="Show only selected tags"/>
- </form>
- <ul id="recipe-list">
- {% for recipe in recipes %}
- <li><a href="recipe?name={{ recipe.clean_name(ascii=True) }}">
- {{ recipe.name }}</li>
- {% endfor %}
- </ul>
-
- <p><a href="edit">add new receipe</a></p>
-{% endblock %}
--- /dev/null
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
--- /dev/null
+from django.conf import settings
+from django.conf.urls.defaults import patterns, include, url
+from django.views.generic import DetailView, ListView
+import taggit.models
+
+import models
+
+# Uncomment the next two lines to enable the admin:
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ url(r'^$', ListView.as_view(
+ queryset=models.Recipe.objects.all().order_by('name'),
+ context_object_name='recipes',
+ template_name='cookbook/recipes.html'),
+ name='recipes'),
+ url(r'^recipe/(?P<pk>\d+)/$', DetailView.as_view(
+ model=models.Recipe, template_name='cookbook/recipe.html'),
+ name='recipe'),
+ url(r'^tags/$', ListView.as_view(
+ queryset=taggit.models.Tag.objects.all(),
+ context_object_name='tags',
+ template_name='cookbook/tags.html'),
+ name='tags'),
+ url(r'^tag/(?P<pk>\d+)/$', 'cookbook.views.tag', name='tag'),
+
+ # Uncomment the admin/doc line below to enable admin documentation:
+ # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+ # Uncomment the next line to enable the admin:
+ url(r'^admin/', include(admin.site.urls), name='admin'),
+
+ url(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to',
+ kwargs={'url': settings.STATIC_URL + 'cookbook.ico'}),
+)
--- /dev/null
+from django.http import HttpResponse
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+
+from taggit.models import Tag
+from .models import Recipe
+
+
+def tag(request, pk):
+ tag = get_object_or_404(Tag, pk=pk)
+ recipes = Recipe.objects.filter(tags__name__in=[tag.slug])
+ return render_to_response(
+ 'cookbook/recipes.html', {'recipes': recipes, 'title': tag.name},
+ RequestContext(request))
+++ /dev/null
-Volumes
-=======
-
-t. teaspoon
-T. tablespoon
-C. cup
-pt. pint
-qt. quart
-gal. gallon
-
-Weights
-=======
-
-oz. ounce
-lb. pound
-
-Lengths
-=======
-
-” inch
-
-Counts
-======
-
-doz. dozen (i.e. 12)
--- /dev/null
+/* <body> */
+
+body {
+ background: #eee;
+}
+
+.fullclear {
+ width:100%;
+ height:1px;
+ margin:0;
+ padding:0;
+ clear:both;
+}
+
+/* </body> */
+
+/* <div id="navigation"> */
+
+#navigation {
+ margin: 0;
+ padding: 4px 0 0 0;
+}
+
+#navigation ul {
+ list-style: none;
+ margin: 0;
+ padding: 0 0 0 12px;
+}
+
+#navigation ul li {
+ margin: 0;
+ padding: 0;
+ float: left;
+}
+
+#navigation ul li a {
+ display: block;
+ font-weight: bold;
+ text-decoration: none;
+ margin: 0;
+ padding: 5px 5px;
+ background-color: #eee;
+}
+
+#navigation ul li a:hover {
+ background-color: #ccc;
+}
+
+#navigation ul li.CurrentSection a {
+ background-color: #FFF;
+}
+
+#navigation ul li.CurrentSection a:hover {
+ background-color: #FFF;
+}
+
+#subnavigation ul li.CurrentSection a {
+ border-width: 0 0 1px 0;
+}
+
+/* </div id="navigation"> */
+
+/* <div id="content"> */
+
+#content {
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ padding-top: 1em;
+ padding-bottom: 1em;
+ background: #fff;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+table.centered {
+ margin: auto;
+}
+
+td, th {
+ padding-right: 1em;
+ text-align: left;
+}
+
+table.wide tr:nth-child(odd) {
+ background: #eee;
+}
+
+table.wide thead tr:nth-child(odd) {
+ background: #ccc;
+}
+
+span.number {
+ float: right; /* so decimal points match up */
+ font-family: monospace; /* so that n-place digits line up */
+ text-align: '.'; /* should work by itself, but browser support is bad */
+}
+
+span.positive {
+ color: green;
+}
+
+span.negative {
+ color: red;
+}
+
+/* </div id="content"> */
--- /dev/null
+{% load i18n adminmedia admin_modify %}
+{% load url from future %}
+<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
+ <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
+{{ inline_admin_formset.formset.management_form }}
+<fieldset class="module">
+ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
+ {{ inline_admin_formset.formset.non_form_errors }}
+ <table>
+ <thead><tr>
+ {% for field in inline_admin_formset.fields %}
+ {% if not field.widget.is_hidden %}
+ <th{% if forloop.first %} colspan="2"{% endif %}{% if field.required %} class="required"{% endif %}>{{ field.label|capfirst }}</th>
+ {% endif %}
+ {% endfor %}
+ {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %}
+ </tr></thead>
+
+ <tbody>
+ {% for inline_admin_form in inline_admin_formset %}
+ {% if inline_admin_form.form.non_field_errors %}
+ <tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
+ {% endif %}
+ <tr class="{% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}"
+ id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
+ <td class="original">
+ {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
+ {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_edit_link and inline_admin_form.original %} <a href="{% url adminform.model_admin.admin_site.app_name|add:':'|add:app_label|add:'_'|add:inline_admin_formset.opts.opts.module_name|add:'_changelist' %}{{ inline_admin_form.original.pk }}">Edit</a>{% endif %}{% endif %}
+ {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
+ </p>{% endif %}
+ {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+ {{ inline_admin_form.fk_field.field }}
+ {% spaceless %}
+ {% for fieldset in inline_admin_form %}
+ {% for line in fieldset %}
+ {% for field in line %}
+ {% if field.is_hidden %} {{ field.field }} {% endif %}
+ {% endfor %}
+ {% endfor %}
+ {% endfor %}
+ {% endspaceless %}
+ </td>
+ {% for fieldset in inline_admin_form %}
+ {% for line in fieldset %}
+ {% for field in line %}
+ <td class="{{ field.field.name }}">
+ {% if field.is_readonly %}
+ <p>{{ field.contents }}</p>
+ {% else %}
+ {{ field.field.errors.as_ul }}
+ {{ field.field }}
+ {% endif %}
+ </td>
+ {% endfor %}
+ {% endfor %}
+ {% endfor %}
+ {% if inline_admin_formset.formset.can_delete %}
+ <td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</fieldset>
+ </div>
+</div>
+
+<script type="text/javascript">
+(function($) {
+ $(document).ready(function($) {
+ var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
+ var alternatingRows = function(row) {
+ $(rows).not(".add-row").removeClass("row1 row2")
+ .filter(":even").addClass("row1").end()
+ .filter(rows + ":odd").addClass("row2");
+ }
+ var reinitDateTimeShortCuts = function() {
+ // Reinitialize the calendar and clock widgets by force
+ if (typeof DateTimeShortcuts != "undefined") {
+ $(".datetimeshortcuts").remove();
+ DateTimeShortcuts.init();
+ }
+ }
+ var updateSelectFilter = function() {
+ // If any SelectFilter widgets are a part of the new form,
+ // instantiate a new SelectFilter instance for it.
+ if (typeof SelectFilter != "undefined"){
+ $(".selectfilter").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}");
+ });
+ $(".selectfilterstacked").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}");
+ });
+ }
+ }
+ var initPrepopulatedFields = function(row) {
+ row.find('.prepopulated_field').each(function() {
+ var field = $(this);
+ var input = field.find('input, select, textarea');
+ var dependency_list = input.data('dependency_list') || [];
+ var dependencies = [];
+ $.each(dependency_list, function(i, field_name) {
+ dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
+ });
+ if (dependencies.length) {
+ input.prepopulate(dependencies, input.attr('maxlength'));
+ }
+ });
+ }
+ $(rows).formset({
+ prefix: "{{ inline_admin_formset.formset.prefix }}",
+ addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
+ formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
+ deleteCssClass: "inline-deletelink",
+ deleteText: "{% trans "Remove" %}",
+ emptyCssClass: "empty-form",
+ removed: alternatingRows,
+ added: (function(row) {
+ initPrepopulatedFields(row);
+ reinitDateTimeShortCuts();
+ updateSelectFilter();
+ alternatingRows(row);
+ })
+ });
+ });
+})(django.jQuery);
+</script>
--- /dev/null
+<html>
+<head>
+ <title>{% block title %}Cookbook{% endblock %}</title>
+ <link rel="stylesheet" href="{{ STATIC_URL }}style.css" />
+ <link rel="shortcut icon" href="{{ STATIC_URL }}cookbook.ico" />
+</head>
+
+<body>
+ <div id="navigation">
+ {% block navigation %}
+ <ul>
+ <li><a href="/">Recipes</a></li>
+ <li><a href="/tags/">Tags</a></li>
+ <li><a href="/admin/">Admin</a></li>
+ </ul>
+ {% endblock %}
+ </div>
+ <div class="fullclear"></div>
+
+ <div id="content">
+ {% block content %}{% endblock %}
+ </div>
+ <div class="fullclear"></div>
+
+ <div id="footer">
+ <p>powered by <a href="http://physics.drexel.edu/~wking/unfolding-disasters/cookbook/">cookbook</a></p>
+ </div>
+</body>
+</html>
--- /dev/null
+{% extends "cookbook/base.html" %}
+
+{% block content %}
+<h1>{{ recipe.name }}</h1>
+
+{% block metadata %}
+<table class="metadata">
+ <tr><td>Author</td><td>{{ recipe.author|default:"unknown" }}</td></tr>
+{% if recipe.source or recipe.url %}
+ <tr><td>Source</td><td><a href="{{ recipe.url }}">{{ recipe.source|default:recipe.url }}</a></td></tr>
+{% endif %}
+{% if recipe.x_yield %}
+ <tr><td>Yield</td><td>{{ recipe.x_yield }}</td></tr>
+{% endif %}
+{% if recipe.tags.all %}
+ <tr><td>Tags</td>
+ <td>
+{% for tag in recipe.tags.all %}
+ <a href="{% url tag tag.id %}">{{ tag }}</a>
+{% endfor %}
+ </td>
+ </tr>
+{% endif %}
+</table>
+{% endblock %}
+
+{% block ingredients %}
+<h2>Ingredients</h2>
+
+{% for ingredient_block in recipe.ingredientblock_set.all %}
+<div class="ingredient_block">
+<h3>{{ ingredient_block.name }}</h3>
+<ul>
+{% for ingredient in ingredient_block.ingredient_set.all %}
+ <li>{{ ingredient }}</li>
+{% endfor %}
+</ul>
+{% if ingredient_block.directions %}
+<h4>Directions</h4>
+
+<div class="directions">
+{{ ingredient_block.directions|safe }}
+</div>
+{% endif %}
+
+</div>
+{% endfor %}
+{% endblock %}
+
+{% block directions %}
+<h2>Directions</h2>
+
+<div class="directions">
+{{ recipe.directions|safe }}
+</div>
+{% endblock %}
+
+{% endblock %}
--- /dev/null
+{% extends "cookbook/base.html" %}
+
+{% block title %}
+{{ title }}
+{% endblock %}
+
+{% block content %}
+<h1>{{ title }}</h1>
+{% if recipes %}
+ <ul>
+ {% for recipe in recipes %}
+ <li><a href="{% url recipe recipe.id %}">{{ recipe.name }}</a></li>
+ {% endfor %}
+ </ul>
+{% else %}
+ <p>No recipes are available.</p>
+{% endif %}
+{% endblock %}
--- /dev/null
+{% extends "cookbook/base.html" %}
+
+{% block title %}
+{{ title }}
+{% endblock %}
+
+{% block content %}
+<h1>{{ title }}</h1>
+{% if tags %}
+ <ul>
+ {% for tag in tags %}
+ <li><a href="{% url tag tag.id %}">{{ tag.name }}</a></li>
+ {% endfor %}
+ </ul>
+{% else %}
+ <p>No tags are available.</p>
+{% endif %}
+{% endblock %}
--- /dev/null
+#!/usr/bin/env python
+from django.core.management import execute_manager
+import imp
+try:
+ imp.find_module('settings') # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
+ sys.exit(1)
+
+import settings
+
+if __name__ == "__main__":
+ execute_manager(settings)
--- /dev/null
+# Django settings for web project.
+
+import os
+import os.path
+
+
+# This is where we'll put the non-Python portions of the app
+# (e.g. templates) and store state information (e.g. the SQLite
+# database). This should be an absolute path.
+DEFAULT_DATA_DIRECTORY = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), 'data'))
+DATA_DIRECTORY = os.environ.get('DJANGO_COOKBOOK_DATA', DEFAULT_DATA_DIRECTORY)
+
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ ('W. Trevor King', 'wking@drexel.edu'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ #'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(DATA_DIRECTORY, 'sqlite3.db'), # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = None # 'America/New_York'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = os.path.join(DATA_DIRECTORY, 'media')
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = '/media/'
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ os.path.join(DATA_DIRECTORY, 'media'),
+ os.path.join(DATA_DIRECTORY, 'static'),
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'wq-)fb$0kv*_q%=ufhaqm7$qw(w84=izd7a!nn)5i@@l)!%3x='
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'cookbook.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ os.path.join(DATA_DIRECTORY, 'template'),
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ # Uncomment the next line to enable the admin:
+ 'django.contrib.admin',
+ # Uncomment the next line to enable admin documentation:
+ # 'django.contrib.admindocs',
+ 'cookbook',
+ 'taggit',
+)
+
+# Required for render_table
+# http://django-tables2.readthedocs.org/en/latest/#render-table
+TEMPLATE_CONTEXT_PROCESSORS = (
+ "django.contrib.auth.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n",
+ "django.core.context_processors.media",
+ "django.core.context_processors.static",
+ "django.contrib.messages.context_processors.messages",
+ "django.core.context_processors.request",
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'class': 'django.utils.log.AdminEmailHandler'
+ }
+ },
+ 'loggers': {
+ 'django.request': {
+ 'handlers': ['mail_admins'],
+ 'level': 'ERROR',
+ 'propagate': True,
+ },
+ }
+}
--- /dev/null
+from django.conf.urls.defaults import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+ # Examples:
+ # url(r'^$', 'web.views.home', name='home'),
+ # url(r'^web/', include('web.foo.urls')),
+
+ # Uncomment the admin/doc line below to enable admin documentation:
+ # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+ # Uncomment the next line to enable the admin:
+ # url(r'^admin/', include(admin.site.urls)),
+)
--- /dev/null
+#!/usr/bin/env python
+
+'Recipe (as in food) managing application for Django'
+
+from distutils.core import setup
+import os.path
+
+from cookbook import __version__
+
+
+_this_dir = os.path.dirname(__file__)
+
+setup(
+ name='cookbook',
+ version=__version__,
+ description=__doc__,
+ long_description=open(os.path.join(_this_dir, 'README'), 'r').read(),
+ author='W. Trevor King',
+ author_email='wking@drexel.edu',
+ license='GPL',
+ url='http://physics.drexel.edu/~wking/unfolding-disasters/posts/cookbook/',
+ packages=['cookbook'],
+ classifiers=[
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Software Development :: Libraries',
+ ],
+)