Last active
August 12, 2021 08:46
-
-
Save bjb/5717314 to your computer and use it in GitHub Desktop.
sample django program: include model info into a form(set) without requiring edits to the model. Useful for a formset that allows performing an action on any of several models.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
run ./unpack.sh to create the django directory structure. | |
make a virtualenv using requirements.txt and enter it. | |
run ./manage.py syncdb to create databases (sqlite3 by default). | |
Now you can run the app with ./manage.py runserver | |
To push back to gist, run ./pack.sh to copy the changed | |
files to where gist expects to find them, and commit | |
as usual. | |
Please update unpack.sh and pack.sh if you add/remove files. | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
cp stuff/templates/index.html stuff_templates_index.html | |
cp stuff/fixtures/initial_data.json stuff_fixtures_initial_data.json | |
cp stuff/tests.py stuff_tests.py | |
cp stuff/forms.py stuff_forms.py | |
cp stuff/views.py stuff_views.py | |
cp stuff/models.py stuff_models.py | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Django==1.3 | |
argparse==1.2.1 | |
distribute==0.6.24 | |
wsgiref==0.1.2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Django settings for trial project. | |
DEBUG = True | |
TEMPLATE_DEBUG = DEBUG | |
import os | |
import logging | |
SITE_ROOT = os.path.dirname((os.path.realpath(__file__))) | |
ADMINS = ( | |
# ('Your Name', '[email protected]'), | |
) | |
MANAGERS = ADMINS | |
DATABASES = { | |
'default': { | |
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. | |
'NAME': 'trial', # Or path to database file if using 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 = 'America/Chicago' | |
# 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 = '' | |
# 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 = '' | |
# 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. | |
) | |
# 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 = 'k#^2x(^@sw@*)(xh&%2w$-yh9e-*1t&z#b%&yiu019p+w!8pii' | |
# 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 = '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. | |
) | |
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', | |
'stuff', | |
) | |
logging.basicConfig ( | |
level = logging.DEBUG, | |
format = '%(asctime)s %(levelname)s %(message)s', | |
filename = os.path.join (SITE_ROOT, 'log/error.log'), | |
filemode = 'w' | |
) | |
# 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, | |
}, | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"model" : "stuff.stuff", | |
"pk" : 1, | |
"fields" : { | |
"name" : "brenda", | |
"number" : 55, | |
"description" : "here's a description", | |
"date" : "2012-08-05" | |
} | |
}, | |
{ | |
"model" : "stuff.stuff", | |
"pk" : 2, | |
"fields" : { | |
"name" : "john", | |
"number" : 15, | |
"description" : "horsey", | |
"date" : "2012-05-25" | |
} | |
}, | |
{ | |
"model" : "stuff.stuff", | |
"pk" : 3, | |
"fields" : { | |
"name" : "debbie", | |
"number" : 65, | |
"description" : "mammoth", | |
"date" : "2012-11-24" | |
} | |
}, | |
{ | |
"model" : "stuff.stuff", | |
"pk" : 4, | |
"fields" : { | |
"name" : "mohan", | |
"number" : 43, | |
"description" : "dinosaur", | |
"date" : "2012-09-05" | |
} | |
} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django import forms | |
from django.utils.translation import ugettext as _ | |
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist, ValidationError | |
from stuff.models import Stuff | |
import sys | |
class MyStuffModelField (forms.IntegerField): | |
def to_python (self, value): | |
pk = super (MyStuffModelField, self).to_python (value) | |
if pk is None: | |
raise ValidationError (_('Invalid id %(pk)s' % {'pk' : pk})) | |
try: | |
obj = Stuff.objects.get (id = pk) | |
except ObjectDoesNotExist, e: | |
raise ValidationError (_('There is no Stuff object with id %(pk)d %(value)s: %(e)s') % | |
{'pk' : pk, 'value' : value, 'e' : e}) | |
except MultipleObjectsReturned, e: | |
raise ValidationError (_('Multiple Stuff objects have id %(id)d: %(e)s') % {'id' : pk, 'e' : e}) | |
return obj | |
class DoActForm (forms.Form): | |
action = forms.BooleanField (required = False) | |
sid = MyStuffModelField (widget = forms.HiddenInput) | |
act_date = forms.DateField (widget = forms.HiddenInput, required = False) | |
model_obj = None | |
def __init__ (self, *args, **kwargs): | |
super (DoActForm, self).__init__(*args, **kwargs) | |
if not self.is_bound: | |
if 'model' in self.initial: | |
self.model_obj = self.initial ['model'] | |
else: | |
raise ValidationError (_('This form must be initialized with a model instance.')) | |
def clean_sid (self): | |
self.model_obj = self.cleaned_data['sid'] | |
return self.cleaned_data['sid'] | |
DoActFormset = forms.formsets.formset_factory (DoActForm, extra = 0) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.db import models | |
from datetime import date | |
class Stuff (models.Model): | |
name = models.CharField (max_length = 32) | |
number = models.IntegerField () | |
date = models.DateField () | |
description = models.CharField (max_length = 256) | |
def __str__ (self): | |
return '%d %s' % (self.id, self.name) | |
def __repr__ (self): | |
return '%s' % (self.__str__()) | |
def __unicode__ (self): | |
return u'%s' % self.__repr__ () | |
def close (self): | |
self.date = date.today () | |
self.save () |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% load i18n %} | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<style type="text/css"> | |
.error { | |
background-color: #FDD; | |
} | |
</style> | |
</head> | |
<body> | |
<form method="post" action="{% url index %}"> | |
{% csrf_token %} | |
{{ formset.management_form }} | |
<h1>Stuff</h1> | |
<table> | |
<tr> | |
<th>Select</th> | |
<th>field</th> | |
<th>id</th> | |
<th>name</th> | |
<th>number</th> | |
<th>date</th> | |
<th>description</th> | |
</tr> | |
{% for form in formset %} | |
<tr{% if form.errors or form.non_field_errors %} class="error"{% endif %}> | |
<td>{{ form.action }}</td> | |
<td>{{ form.sid }}</td> | |
<td>{{ form.model_obj.id }}</td> | |
<td>{{ form.model_obj.name }}</td> | |
<td>{{ form.model_obj.number }}</td> | |
<td>{{ form.model_obj.date }}</td> | |
<td>{{ form.model_obj.description }} {{ form.act_date }}</td> | |
</tr> | |
{% endfor %} | |
</table> | |
<div> | |
<h3>errors</h3> | |
<div>{{ formset.errors }}</div> | |
</div> | |
<div> | |
<h3>non_form_errors</h3> | |
<div>{{ formset.non_form_errors }}</div> | |
</div> | |
<input type="submit" id="submit" value="Submit" /> | |
</form> | |
</body> | |
</html> | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.test import TestCase | |
from django import forms | |
from stuff.views import index | |
from stuff.forms import DoActForm | |
from stuff.models import Stuff | |
import datetime | |
import sys | |
class FormSetTest (TestCase): | |
def setUp (self): | |
self.stuffs = [] | |
self.stuffs.append (Stuff.objects.get (id = 1)) | |
self.stuffs.append (Stuff.objects.get (id = 2)) | |
self.stuffs.append (Stuff.objects.get (id = 3)) | |
self.stuffs.append (Stuff.objects.get (id = 4)) | |
self.badstuff = Stuff () | |
self.badstuff.id = 666 | |
self.badstuff.name = 'fred' | |
self.badstuff.number = 1352 | |
self.badstuff.date = datetime.date (1984, 8, 19) | |
self.good_data_formset = { | |
'form-TOTAL_FORMS' : 1, | |
'form-INITIAL_FORMS' : 1, | |
'form-MAX_NUM_FORMS' : 15, | |
'form-0-action' : True, | |
'form-0-sid' : self.stuffs[0].id, | |
'form-0-act_date' : '2013-06-11', | |
} | |
self.bad_data_formset = { | |
'form-TOTAL_FORMS' : 1, | |
'form-INITIAL_FORMS' : 1, | |
'form-MAX_NUM_FORMS' : 15, | |
'form-0-action' : True, | |
'form-0-sid' : self.badstuff.id, | |
'form-0-act_date' : '2013-06-11', | |
} | |
self.bad_data_formset_multiple = { | |
'form-TOTAL_FORMS' : 4, | |
'form-INITIAL_FORMS' : 4, | |
'form-MAX_NUM_FORMS' : 15, | |
'form-0-action' : True, | |
'form-0-sid' : self.stuffs[0].id, | |
'form-0-act_date' : '2013-06-11', | |
'form-1-action' : False, | |
'form-1-sid' : self.stuffs[1].id, | |
'form-1-act_date' : '2013-06-11', | |
'form-2-action' : True, | |
'form-2-sid' : self.badstuff.id, | |
'form-2-act_date' : '2013-06-11', | |
'form-3-action' : True, | |
'form-3-sid' : self.stuffs[3].id, | |
'form-3-act_date' : '2013-06-11', | |
} | |
self.bad_data_formset_resubmit = { | |
'form-TOTAL_FORMS' : 4, | |
'form-INITIAL_FORMS' : 4, | |
'form-MAX_NUM_FORMS' : 15, | |
'form-0-action' : True, | |
'form-0-sid' : self.stuffs[0].id, | |
'form-0-act_date' : '2013-06-11', | |
'form-1-action' : False, | |
'form-1-sid' : self.stuffs[1].id, | |
'form-1-act_date' : '2013-06-11', | |
'form-2-action' : False, | |
'form-2-sid' : self.badstuff.id, | |
'form-2-act_date' : '2013-06-11', | |
'form-3-action' : True, | |
'form-3-sid' : self.stuffs[3].id, | |
'form-3-act_date' : '2013-06-11', | |
} | |
def tearDown (self): | |
pass | |
def test_formset_get_valid (self): | |
response = self.client.get ('/index') | |
self.assertEqual (response.status_code, 200) | |
self.assertTemplateUsed (response, 'index.html', 'test_formset_get_valid') | |
def test_formset_post_valid (self): | |
response = self.client.post ('/index', self.good_data_formset) | |
self.assertEqual (response.status_code, 302) | |
def test_formset_post_not_valid_contains (self): | |
response = self.client.post ('/index', self.bad_data_formset) | |
self.assertEqual (response.status_code, 200) | |
def test_formset_post_not_valid_multiple (self): | |
response = self.client.post ('/index', self.bad_data_formset_multiple) | |
self.assertEqual (response.status_code, 200) | |
self.assertContains (response, 'There is no Stuff object with id 666') | |
def test_formset_post_not_valid_resubmit (self): | |
response = self.client.post ('/index', self.bad_data_formset_resubmit) | |
self.assertEqual (response.status_code, 302) | |
class FormTest (TestCase): | |
def setUp (self): | |
self.stuffs = [] | |
self.stuffs.append (Stuff.objects.get (id = 1)) | |
self.stuffs.append (Stuff.objects.get (id = 2)) | |
self.stuffs.append (Stuff.objects.get (id = 3)) | |
self.stuffs.append (Stuff.objects.get (id = 4)) | |
self.badstuff = Stuff () | |
self.badstuff.id = 666 | |
self.badstuff.name = 'fred' | |
self.badstuff.number = 1352 | |
self.badstuff.date = datetime.date (1984, 8, 19) | |
def tearDown (self): | |
pass | |
def test_form_without_model (self): | |
data = {} | |
data['sid'] = self.stuffs[0].id | |
data['act_date'] = datetime.date (2013, 6, 12) | |
self.assertRaises (forms.ValidationError, DoActForm, initial = data) | |
def test_form_with_model_initial (self): | |
data = {} | |
data['sid'] = self.stuffs[1].id | |
data['act_date'] = datetime.date (2013, 6, 12) | |
data['model'] = self.stuffs[1] | |
ff = DoActForm (initial = data) | |
self.assertEqual (self.stuffs[1].id, ff['sid'].value()) | |
def test_form_with_model_data (self): | |
data = {} | |
data['action'] = True | |
data['sid'] = self.stuffs[0].id | |
data['act_date'] = datetime.date (2013, 6, 12) | |
data['model'] = self.stuffs[0] | |
ff = DoActForm (data) | |
ff.is_valid () | |
self.assertEqual (self.stuffs[0], ff.cleaned_data['sid']) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.shortcuts import render_to_response | |
from django.http import HttpResponseRedirect | |
from django.contrib.auth.decorators import login_required | |
from django.template import RequestContext | |
from django.core.urlresolvers import reverse | |
import logging | |
import traceback | |
import pprint | |
import datetime | |
from stuff.models import Stuff | |
from stuff.forms import DoActFormset | |
def index (request): | |
asof_date = datetime.date.today () | |
if 'POST' == request.method: | |
formset = DoActFormset (request.REQUEST) | |
if formset.is_valid (): # run the validations | |
for form in formset: | |
if form.cleaned_data['action']: | |
obj_thing = form.cleaned_data['sid'] | |
obj_thing.close () | |
return HttpResponseRedirect (reverse ('index')) | |
else: | |
# if we have only chosen valid forms, go ahead and process the formset | |
# this allows us to unselect the invalid forms and resubmit | |
process = True | |
# if there are any selected invalid forms, do not process any valid forms | |
for form in formset: | |
if not form.is_valid (): | |
# test if action was selected. Hmm. | |
if form['action'].value (): | |
process = False | |
if process: | |
for form in formset: | |
if form.is_valid () and form.cleaned_data['action']: | |
obj_thing = form.cleaned_data['sid'] | |
obj_thing.close () | |
return HttpResponseRedirect (reverse ('index')) | |
else: | |
s_data = [] | |
ss = Stuff.objects.all ().order_by ('id') | |
for s_item in ss: | |
data = {} | |
data['sid'] = s_item.id | |
data['act_date'] = asof_date | |
data['model'] = s_item | |
s_data.append (data) | |
formset = DoActFormset (initial = s_data) | |
return render_to_response ('index.html', { | |
'formset' : formset, | |
}, | |
RequestContext (request)) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
mkdir log | |
mkdir stuff | |
mkdir stuff/fixtures | |
mkdir stuff/templates | |
cp stuff_models.py stuff/models.py | |
cp stuff_views.py stuff/views.py | |
cp stuff_forms.py stuff/forms.py | |
cp stuff_tests.py stuff/tests.py | |
cp stuff_fixtures_initial_data.json stuff/fixtures/initial_data.json | |
cp stuff_templates_index.html stuff/templates/index.html | |
touch __init__.py | |
touch stuff/__init__.py | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'^$', 'stuff.views.home', name='home'), | |
# url(r'^trial/', include('trial.foo.urls')), | |
url (r'^$', 'stuff.views.index', name='index'), | |
url (r'^index$', 'stuff.views.index', name='index'), | |
# 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)), | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment