Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielgrant committed Dec 19, 2010
0 parents commit 17cd91c
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
*.py[co]
43 changes: 43 additions & 0 deletions README
@@ -0,0 +1,43 @@
{% if form %}To edit this page, click <a href="#my_editable_content" class="inline_edit_button">here</a>{% endif %}

<div id="my_editable_content">
<div class="inline_editable_content">
<h2>{{ object.title }}</h2>
{{ object.body }}
</div>
<form action="" method="post" class="inline_editable_form">
{{ form }}
<input type="submit" value="Save" />
</form>
</div>

class Author(models.Model):
name = models.CharField(max_length=100)

class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)


InlineUpdateView().as_view(model=Publisher)

uses "books/publisher_inline_form.html"



from django.forms.models import inlineformset_factory
from embedded_inline_form import ModelForm

class AuthorBooksForm(ModelForm):
class Meta:
model = Author
class Forms:
inlines = {
'books': inlineformset_factory(Author, Book),
}

class AuthorUpdateView(UpdateView):
form_class = AuthorBooksForm
model = Author

AuthorUpdateView().as_view()
Empty file added inline_edit/__init__.py
Empty file.
114 changes: 114 additions & 0 deletions inline_edit/embedded_inline_form.py
@@ -0,0 +1,114 @@
# Copyright (c) 2010, Stanislas Guerra.
# All rights reserved.
# This document is licensed as free software under the terms of the
# BSD License: http://www.opensource.org/licenses/bsd-license.php


from django.forms.models import ModelFormMetaclass


class ModelFormOptions(object):
def __init__(self, options=None):
self.inlines = getattr(options, 'inlines', {})

class ModelFormMetaclass(ModelFormMetaclass):
def __new__(cls, name, bases, attrs):
new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases, attrs)
new_class._forms = ModelFormOptions(getattr(new_class, 'Forms', None))
return new_class

class ModelForm(forms.ModelForm):
"""
Add to ModelForm the ability to declare inline formsets.
It save you from the boiler-plate implementation of cross validation/saving of such
forms in the views.
You should use It in the admin's forms if you need the inherit them in your apps
because there is not multi-inherance.
>>> class Program(models.Model):
... name = models.CharField(max_length=100, blank=True)
>>> class ImageProgram(models.Model):
... image = models.ImageField('image')
... program = models.ForeignKey(Programm)
>>> class Ringtone(models.Model):
... sound = models.FileField('sound')
... program = models.ForeignKey(Programm)
Use It in your admin.py instead of django.forms.ModelForm:
>>> class ProgramAdminForm(ModelForm):
... class Meta:
... model = Program
... def clean(self):
... cleaned_data = self.cleaned_data
... # stuff
... return cleaned_data
In your app, say you declare the following inline formsets:
>>> ImageProgramFormSet = inlineformset_factory(Program, ImageProgram,
... form=ImageProgramForm, max_num=6)
>>> RingToneFormSet = inlineformset_factory(Program, RingTone, form=RingtoneProgramForm)
You can bind them in your program's form:
>>> class MyProgramForm(ProgramAdminForm):
... class Forms:
... inlines = {
... 'images': ImageProgramFormSet,
... 'ringtones': RingToneFormSet,
... }
And instanciate It:
>>> program_form = MyProgramForm(request.POST, request.FILES, prefix='prog')
In the template, you access the inlines like that :
{{ program_form.inlineformsets.images.management_form }}
{{ program_form.inlineformsets.images.non_form_errors }}
<table>
{{ program_form.inlineformsets.images.as_table }}
</table>
"""

__metaclass__ = ModelFormMetaclass

def __init__(self, *args, **kwargs):
super(ModelForm, self).__init__(*args, **kwargs)
if hasattr(self._forms, 'inlines'):
self.inlineformsets = {}
for key, FormSet in self._forms.inlines.items():
self.inlineformsets[key] = FormSet(self.data or None, self.files or None,
prefix=self._get_formset_prefix(key),
instance=self.instance)

def save(self, *args, **kwargs):
instance = super(ModelForm, self).save(*args, **kwargs)
if hasattr(self._forms, 'inlines'):
for key, FormSet in self._forms.inlines.items():
fset = FormSet(self.data, self.files, prefix=self._get_formset_prefix(key),
instance=instance)
fset.save()
return instance

def has_changed(self, *args, **kwargs):
has_changed = super(ModelForm, self).has_changed(*args, **kwargs)
if has_changed:
return True
else:
for fset in self.inlineformsets.values():
for i in range(0, fset.total_form_count()):
form = fset.forms[i]
if form.has_changed():
return True
return False

def _get_formset_prefix(self, key):
return u'%s_%s' % (self.prefix, key.upper())

def _clean_form(self):
super(ModelForm, self)._clean_form()
for key, fset in self.inlineformsets.items():
for i in range(0, fset.total_form_count()):
f = fset.forms[i]
if f.errors:
self._errors['_%s_%d' %(fset.prefix, i)] = f.non_field_errors
3 changes: 3 additions & 0 deletions inline_edit/models.py
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
10 changes: 10 additions & 0 deletions inline_edit/static/inline_edit/inline_edit.js
@@ -0,0 +1,10 @@

jquery_wrapper($){
$(function(){
$("inline_edit_button").click(function(){
editable = $(this).attr("href");
$(editable).find("inline_editable_content").hide();
$(editable).find("inline_editable_form").show();
}
});
)(jquery);
84 changes: 84 additions & 0 deletions inline_edit/views.py
@@ -0,0 +1,84 @@
from django.views import View, DetailView, UpdateView
from django.utils.decorators import classonlymethod
from django.models import Permission
from django.core.exceptions import ImproperlyConfigured


def conditional_dispatch(condition_func, true_view, false_view):
def dispatch(request, *args, **kwargs):
if condition_func(request, *args, **kwargs):
return true_view(request, *args, **kwargs)
else:
return false_view(request, *args, **kwargs)
return dispatch

def dict_diff(dict1, dict2):
diff_set = set(dict1.iteritems()) - set(dict2.iteritems())
return dict(diff_set)

class ConditionalDispatchView(object):
"""
TODO: allow overriding this class, to set items on both subclasses
"""
class Meta:
pass
@classonlymethod
def as_view(cls, condition_func=None,
true_view_class=None, false_view_class=None, **initkwargs
):
if true_view_class is None
try:
true_view_class = cls.Meta.true_view_class
except AttributeError:
raise ImproperlyConfigured(
"No view to direct to when condition_function returns"
" True. Either provide a true_view_class, or define a"
" true_view_class property on this view's Meta class."
)
if false_view_class is None
try:
false_view_class = cls.Meta.false_view_class
except AttributeError:
raise ImproperlyConfigured(
"No view to direct to when condition_function returns"
" False. Either provide a false_view_class, or define a"
" false_view_class property on this view's Meta class."
)

# get properties defined on this subclass as base initkwargs
base_initkwargs = dict_diff(cls, ConditionalDispatchView)
base_initkwargs.update(initkwargs)

# remove the Meta class; that's for us, not the view class
base_initkwargs.pop('Meta', None)

# create class-specific keyword arguments
true_initkwargs = {}
false_initkwargs = {}

for k, v in base_initkwargs.iter_items():
if hasattr(true_view_class, k):
true_initkwargs[key] = v
if hasattr(false_view_class, k):
false_initkwargs[key] = v

true_view = true_view_class().as_view(**true_initkwargs)
false_view = false_view_class().as_view(**false_initkwargs)

if condition_func is None:
# default condition_func checks if user 'can_change' the object
model = true_view_class(**true_initkwargs).get_queryset().model
app_label = model._meta.app_label
change_perm = model._meta.get_change_permission()
def condition_func(request, *args, **kwargs)
return request.user.has_perm(app_label + '.' + change_perm)

return conditional_dispatch(condition_func, true_view, false_view)

class InlineUpdateView(ConditionalDispatchView):
template_name_suffix = '_inline_form'
class Meta:
true_view_class = UpdateView
false_view_class = DetailView


39 changes: 39 additions & 0 deletions run_tests.py
@@ -0,0 +1,39 @@

# from http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/

from django.conf import settings
from django.core.management import call_command

def main():
# Dynamically configure the Django settings with the minimum necessary to
# get Django running tests
settings.configure(
INSTALLED_APPS=(
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.admin',
'django.contrib.sessions',
'multiforloop',
),
# Django replaces this, but it still wants it. *shrugs*
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/tmp/django_login.db',
}
},
MEDIA_ROOT = '/tmp/django_login_test_media/',
ROOT_URLCONF = '',
DEBUG = True,
TEMPLATE_DEBUG = True,
)

#call_command('syncdb')

# Fire off the tests
call_command('test', 'multiforloop')


if __name__ == '__main__':
main()

13 changes: 13 additions & 0 deletions setup.py
@@ -0,0 +1,13 @@
from distutils.core import setup

setup(
name='django-inline-edit',
version='0.1dev',
packages=['inline_edit',],
author='Gabriel Grant',
author_email='g@briel.ca',
license='LGPL',
long_description=open('README').read(),
url='http://github.org/gabrielgrant/django-inline-edit/',
)

0 comments on commit 17cd91c

Please sign in to comment.