auto generate forms

This commit is contained in:
Sanj 2011-02-01 20:43:30 +05:30
parent e416b308cd
commit 1fd9684064
13 changed files with 413 additions and 105 deletions

View File

@ -17,14 +17,17 @@ from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404, redirect
from django.template import RequestContext
from django.conf import settings
from django.contrib.comments.forms import CommentForm
from django.contrib.comments.models import Comment
from django.contrib.comments.signals import comment_will_be_posted, comment_was_posted
from django.contrib.contenttypes.models import ContentType
from ox.utils import json
from ox.django.decorators import login_required_json
from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response
from ox.django.http import HttpFileResponse
import ox
from actions import actions
from user.models import get_user_json
def api(request):
if request.META['REQUEST_METHOD'] == "OPTIONS":
@ -84,11 +87,7 @@ def api(request):
# return render_to_json_response(json_response({'actions': docs}))
#FIXME: REMOVE THIS FUNCTION WHEN THERE ARE REAL USERS!!!!
def get_user_json(u):
return {'name': 'Guest', 'group': 'guest', 'preferences': {}}
def hello(request):
def init(request):
'''
return {'status': {'code': int, 'text': string},
'data': {user: object}}
@ -98,9 +97,15 @@ def hello(request):
if request.user.is_authenticated():
response['data']['user'] = get_user_json(request.user)
else:
response['data']['user'] = {'name': 'Guest', 'group': 'guest', 'preferences': {}}
response['data']['user'] = {'name': 'Guest', 'group': 'guest', 'level': 'guest', 'preferences': {}}
#FIXME: Get config definition out of here.
response['data']['config'] = {
'site': {
'name': 'India Theatre Forum'
}
}
return render_to_json_response(response)
actions.register(hello)
actions.register(init)
def error(request):
'''
@ -157,17 +162,159 @@ actions.register(preview)
#FIXME: Generalize based on these two functions being the same.
def info(request):
'''
id: object id
model: string, name of model
module: string, name of module
'''
data = json.loads(request.POST['data'])
id = int(data['id'])
model = getModel(data)
response = json_response({})
response['status'] = {'code': 200}
obj = get_object_or_404(model, pk=id)
if model.hasComments:
response['commentForm'] = _get_comment_form_initial(request, obj)
response['comments'] = _get_comments(obj)
response['data'] = obj.info_dict()
response['template'] = getTemplate(data, "info")
return render_to_json_response(response)
actions.register(info)
#obj: Django model instance - object for which to get comment form for.
#Returns dict with content_type, object_id, security_hash, etc.
#FIXME: put comments stuff somewhere
def _get_comment_form_initial(request, obj):
c = CommentForm(obj)
return c.initial
def _get_comments(obj):
ret = []
content_type = ContentType.objects.get_for_model(obj)
object_id = obj.id
qset = Comment.objects.filter(content_type=content_type, object_pk=object_id)
for q in qset:
ret.append({
'name': q.name,
# 'date': q.submit_date,
'comment': q.comment
})
return ret
@login_required_json
def addComment(request):
'''
id
module
model
comment
content_type
email
name
object_pk
security_hash
timestamp
'''
data = json.loads(request.POST['data'])
data.update({
'name': request.user.username,
'email': request.user.email
})
id = int(data['object_pk'])
model = getModel(data)
obj = get_object_or_404_json(model, pk=id)
cf = CommentForm(obj, data=data)
ret = json_response({})
if cf.is_valid():
comment = cf.get_comment_object()
comment.user = request.user
# Signal that the comment is about to be saved
responses = comment_will_be_posted.send(
sender = comment.__class__,
comment = comment,
request = request
)
for (receiver, response) in responses:
if response == False:
return CommentPostBadRequest(
"comment_will_be_posted receiver %r killed the comment" % receiver.__name__)
# Save the comment and signal that it was saved
comment.save()
comment_was_posted.send(
sender = comment.__class__,
comment = comment,
request = request
)
ret['data'] = {
id: comment.id
}
ret['status'] = {'code': 200}
else:
ret['status'] = {'code': 200}
ret['errors'] = cf.errors
return render_to_json_response(ret)
actions.register(addComment)
widgetMap = {
'TextInput': 'text',
'Textarea': 'textarea',
'Select': 'select'
}
def getAddItemForm(request):
response = json_response({})
data = json.loads(request.POST['data'])
model = getModel(data)
# import pdb; pdb.set_trace()
form = getForm(data)
form_fields = []
for field in form.base_fields:
name = field
this = form.base_fields[field]
widget = this.widget
widgetClassName = type(widget).__name__
if hasattr(widget, "choices"):
choices = []
for c in widget.choices:
choices.append({
'id': c[0],
'title': c[1]
})
else:
choices = False
js_widget = widgetMap.get(widgetClassName, "text")
form_fields.append({
'name': name,
'widget': js_widget,
'label': this.label,
'choices': choices
})
response['data']['form'] = form_fields
return render_to_json_response(response)
actions.register(getAddItemForm)
def addItem(request):
response = json_response({})
data = json.loads(request.POST['data'])
model = getModel(data)
form = getForm(data)
if form.is_valid():
m = model()
for field in form.base_fields:
m.__setattr__(field, form.cleaned_data[field])
m.save()
response['data'] = {'id': m.id}
else:
response['errors'] = form.errors
return render_to_json_response(response)
actions.register(addItem)
def getTemplate(data, tmpl_name):
path = join(settings.PROJECT_PATH, "templates", data['module'], data['model'], tmpl_name + ".html")
@ -177,6 +324,14 @@ def getModel(data):
module = __import__(data['module'])
return module.models.__getattribute__(data['model'])
#FIXME: BAAD Hack
def getForm(data):
module = __import__(data['module'] + ".forms")
model = getModel(data)
formStr = model.add_form
return module.forms.__getattribute__(formStr)
def get_api_doc(f):
f = 'api_' + f

View File

@ -15,6 +15,7 @@ class ItfModel(models.Model):
fts_fields = []
fk_filters = []
sort_fields = []
hasComments = True
class Meta:
abstract = True
@ -110,3 +111,16 @@ def getField(fields, name):
return f
return False
def site_config():
with open(settings.SITE_CONFIG) as f:
site_config = json.load(f)
site_config['keys'] = {}
for key in site_config['itemKeys']:
site_config['keys'][key['id']] = key
site_config['_findKeys'] = {}
for key in site_config['findKeys']:
site_config['_findKeys'][key['id']] = key
return site_config

View File

@ -13,6 +13,8 @@ def index(request):
return html_snapshot(request)
return render_to_response('index.html', context)
'''
def site_json(request):
data = {
'site': {
@ -20,4 +22,4 @@ def site_json(request):
}
}
return render_to_json_response(data)
'''

View File

@ -0,0 +1,7 @@
from django.forms import ModelForm
from models import BestPractice
class BestPracticeForm(ModelForm):
class Meta:
model = BestPractice

View File

@ -2,6 +2,8 @@ from django.db import models
from tagging.fields import TagField
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from app.models import ItfModel
# from django.forms import
# import forms
class BestPractice(ItfModel):
title = models.CharField(max_length=512)
@ -12,13 +14,14 @@ class BestPractice(ItfModel):
theatre = models.TextField(blank=True, help_text="Spotlight on Theatre text")
quick_howto = models.TextField(blank=True)
tags = TagField(blank=True, help_text="Enter as many tags as you like, separated by commas.")
category = models.ForeignKey("BestPracticeCategory")
category = models.ForeignKey("BestPracticeCategory", null=True)
added = models.DateTimeField(auto_now_add=True, null=True)
modified = models.DateTimeField(auto_now=True, null=True)
fts_fields = ['title', 'story', 'guideline', 'law', 'theatre', 'quick_howto']
fk_filters = ['category']
sort_fields = ['title']
add_form = "BestPracticeForm"
def __unicode__(self):
return str(self.id) + ": " + self.title
@ -103,3 +106,7 @@ class Glossary(models.Model):
def __unicode__(self):
return self.term

View File

@ -16,6 +16,7 @@ LOGGING_OUTPUT_ENABLED = True
PROJECT_PATH = os.path.dirname(__file__)
PROJECT_ROOT = PROJECT_PATH
SITE_CONFIG = join(PROJECT_ROOT, 'itf.json')
CKEDITOR_MEDIA_PREFIX = "/static/ckeditor/"
CKEDITOR_UPLOAD_PATH = join(PROJECT_PATH, "static/upload/images/")
@ -137,6 +138,7 @@ INSTALLED_APPS = (
'debug_toolbar',
'sorl.thumbnail',
'south',
'user',
'ckeditor',
)

View File

@ -10,3 +10,11 @@
.itfPreviewTitle {
font-weight: bold;
}
.OxCommentWrapper {
margin-bottom: 4px;
}
.OxCommentsWrapper {
margin-top: 6px;
}

View File

@ -1,21 +1,16 @@
var app = new Ox.App({
apiURL: '/api/',
init: 'hello',
init: 'init',
config: 'site.json' //FIXME: shouldn't need this, get data with 'hello' or 'init'.
});
/*
app.api.hell = function() {
setTimeout("app.api.hello()", 1000);
}
*/
app.launch(function(data) {
Ox.theme("classic");
Ox.print(data);
app.$body = $('body');
app.$document = $(document);
app.$window = $(window);
app.$window = $(window);
app.$window.resize(function() {
ITF.setSizes();
});
@ -37,6 +32,10 @@ app.launch(function(data) {
*/
var wrapper = app.construct.wrapper();
app.$body.css({'opacity': 0});
//FIXME: user handling should be cleaner?
if (data.user.level != 'guest') {
ITF.login(data);
}
wrapper.appendTo(app.$body);
ITF.setSizes();
app.$body.animate({

View File

@ -472,9 +472,12 @@ BEGIN mainPanel
id: id
});
var btnsWrapper = new Ox.Element().css({'textAlign': 'center', 'marginTop': '4px'});
var loginBtn = new Ox.Button({
var btnsWrapper = c.$btns = new Ox.Element().css({'textAlign': 'center', 'marginTop': '4px'});
var info = c.$info = new Ox.Element("span").appendTo(btnsWrapper);
var loginBtn = c.$loginBtn = new Ox.Button({
'id': 'loginBtn',
'title': 'Login',
'size': 'large'
@ -482,7 +485,7 @@ BEGIN mainPanel
ui.accountDialog("login").open();
}).appendTo(btnsWrapper);
var registerBtn = new Ox.Button({
var registerBtn = c.$registerBtn = new Ox.Button({
'id': 'registerBtn',
'title': 'Register',
'size': 'large'
@ -490,62 +493,18 @@ BEGIN mainPanel
ui.accountDialog("register").open();
}).appendTo(btnsWrapper);
btnsWrapper.appendTo(c);
/*
var registerForm = (function() {
var u = ui.accountDialogOptions('register');
var btns = u.buttons;
var content = u.content;
var title = u.title;
// debugger;
var e = new Ox.Element().html(title).append(content);
var btnsWrapper = new Ox.Element().css({'textAlign': 'center', 'marginTop': '4px'});
var regBtn = btns[1][1].appendTo(btnsWrapper);
var loginBtn = new Ox.Button({
id: 'loginBtn',
title: 'Login'
}).bindEvent("click", function() {
// app.$ui.accountDialog = new Ox.Element();
var l = ui.accountDialogOptions("login");
var loginContent = l.content;
var loginSubmitBtn = l.buttons[1][1];
var loginTitle = l.title;
var d = new Ox.Dialog({
id: 'loginDialog',
content: loginContent,
buttons: [
new Ox.Button({
id: 'cancel',
title: 'Cancel',
}).bindEvent("click", function() { d.close(); }),
loginSubmitBtn
],
title: loginTitle
});
d.open();
var logoutBtn = c.$logoutBtn = new Ox.Button({
'id': 'logoutBtn',
'title': 'Logout',
'size': 'large'
}).bindEvent("click", function() {
app.api.logout({}, function(data) {
ui.accountLogoutDialog().open();
});
loginBtn.appendTo(btnsWrapper);
btnsWrapper.appendTo(e);
return e;
})();
*/
}).appendTo(btnsWrapper).hide();
// registerForm.appendTo(c);
/*
var $registerTitle = new Ox.Element().html("Register:").appendTo(c);
var registerForm = c.$form = new Ox.Form({
id: "registerForm",
items: $registerFormItems,
submit: function(data, callback) {
alert(JSON.stringify(data));
app.api.register(data, function(result) {
alert(JSON.stringify(result));
});
}
*/
// c.html("login goes here");
btnsWrapper.appendTo(c);
return c;
},

View File

@ -1,6 +1,6 @@
/*
var $registerFormItems = (function() {
var $username = new Ox.Input({
'id': 'registerUsername',
@ -41,9 +41,14 @@ var $registerFormItems = (function() {
return [$username, $password, $email]
})();
*/
(function() {
var ui = {
//FIXME: creating global for ui. This doesn't sound healthy.
ui = {
accountDialog: function(action) {
var that = app.$ui.accountDialog = new Ox.Dialog($.extend({
height: 256,
@ -152,7 +157,7 @@ var $registerFormItems = (function() {
});
}
var items = {
'login': ['username', 'password'],
'login': ['usernameOrEmail', 'password'],
'register': ['newUsername', 'password', 'email'],
'reset': ['usernameOrEmail'],
'resetAndLogin': ['oldUsername', 'newPassword', 'code']
@ -165,19 +170,19 @@ var $registerFormItems = (function() {
items: $items,
submit: function(data, callback) {
if (action == 'login') {
pandora.api.login(data, function(result) {
app.api.login(data, function(result) {
if (!result.data.errors) {
app.$ui.accountDialog.close();
login(result.data);
ITF.login(result.data);
} else {
callback([{id: 'password', message: 'Incorrect password'}]);
}
});
} else if (action == 'register') {
pandora.api.register(data, function(result) {
app.api.register(data, function(result) {
if (!result.data.errors) {
app.$ui.accountDialog.close();
login(result.data);
ITF.login(result.data);
ui.accountWelcomeDialog().open();
} else {
callback([{id: 'password', message: result.data.errors.toString()}]); // fixme
@ -188,7 +193,7 @@ var $registerFormItems = (function() {
key = usernameOrEmail[0].id;
data = {};
data[key] = usernameOrEmail[1];
pandora.api.requestToken(data, function(result) {
app.api.requestToken(data, function(result) {
if (!result.data.errors) {
app.$ui.accountDialog.options(ui.accountDialogOptions('resetAndLogin', result.data.username));
} else {
@ -196,10 +201,10 @@ var $registerFormItems = (function() {
}
});
} else if (action == 'resetAndLogin') {
pandora.api.resetPassword(data, function(result) {
app.api.resetPassword(data, function(result) {
if (!result.data.errors) {
app.$ui.accountDialog.close();
login(result.data);
ITF.login(result.data);
} else {
callback([{id: 'code', message: 'Incorrect code'}]);
}
@ -353,8 +358,8 @@ var $registerFormItems = (function() {
title: 'Logout'
}).bindEvent('click', function() {
that.close();
pandora.api.logout({}, function(result) {
logout(result.data);
app.api.logout({}, function(result) {
ITF.logout(result.data);
});
})
],
@ -433,7 +438,7 @@ function validateUser(key, existing) {
var string = key == 'username' ? 'username' : 'e-mail address';
return function(value, callback) {
var valid = key == 'username' ? !!value.length : Ox.isValidEmail(value);
valid ? pandora.api.findUser({
valid ? app.api.findUser({
key: key,
value: value,
operator: '='
@ -453,4 +458,4 @@ function validateUser(key, existing) {
};
}
})();

View File

@ -1,6 +1,31 @@
var ITF = {};
ITF.login = function(data) {
app.user = data.user;
var box = app.$ui.loginBox;
box.slideUp("slow", function() {
box.$loginBtn.hide();
box.$registerBtn.hide();
box.$info.html("logged in as " + data.user.username + " ");
box.$logoutBtn.show();
box.slideDown();
});
}
ITF.logout = function(data) {
app.user = data.user;
var box = app.$ui.loginBox;
box.slideUp("slow", function() {
box.$logoutBtn.hide();
box.$info.html('');
box.$loginBtn.show();
box.$registerBtn.show();
box.slideDown();
});
// alert("logged out");
}
ITF.setSizes = function() {
var mp = app.$ui.middlePanel;
var winHeight = parseInt(mp.height());
@ -18,6 +43,10 @@ ITF.setSizes = function() {
mmp.size(2, panelWidth);
mbp.size(0, panelWidth);
mbp.size(2, panelWidth);
//FIXME: FIX!
var listHeight = panelHeight - 22;
app.$ui.bestpracticesBox.$listContainer.css({'height': listHeight + "px"});
}

View File

@ -51,13 +51,81 @@ Ox.ItfBox = function(options, self) {
$additionalButtons.appendTo($buttons);
}
var $maximizeBtn = new Ox.Button({
id: options.id + "Maximize",
var $addBtn = new Ox.Button({
id: options.id + "AddItem",
style: 'symbol',
title: 'add',
type: 'image',
tooltip: 'Maximize'
}).appendTo($buttons);
tooltip: 'Add'
})
.appendTo($buttons)
.bindEvent("click", function() {
app.$ui[options.boxId].$loading.start();
app.api.getAddItemForm({
'module': that.options("module"),
'model': that.options("model")
}, function(response) {
app.$ui[options.boxId].$loading.stop();
var form = response.data.form;
var $content = new Ox.Element();
var $items = [];
for (var i=0; i<form.length; i++) {
var field = form[i];
switch (field.widget) {
case 'select':
// alert(field.label);
var $input = new Ox.Select({
'items': field.choices,
'label': field.label,
'id': field.name,
'width': 256
});
// $items.push($input);
break;
default:
// alert("default: " + field.label);
var $input = new Ox.Input({
'type': field.widget,
'id': field.name,
'label': field.label,
'labelWidth': 128,
'width': 384
});
$items.push($input);
}
// $items.push($input);
}
Ox.print("items", $items);
var $form = new Ox.Form({
'id': 'AddItem' + that.options("model"),
'items': $items,
'submit': function(data, callback) {
data['model'] = that.options("model");
data['module'] = that.options("module");
app.api.addItem(data, function(result) {
alert(JSON.stringify(result));
});
}
}).appendTo($content);
var d = new Ox.Dialog({
buttons: [
new Ox.Button({
'id': 'SubmitItem' + that.options("model"),
'size': 'medium',
'title': 'Submit'
}).bindEvent("click", function() { $form.submit(); }),
new Ox.Button({
'id': 'cancel',
'title': 'Cancel',
}).bindEvent("click", function() { d.close(); })
],
content: $content,
width: 600,
height: 500,
title: 'Add Best Practice'
}).open();
});
});
var $infoBtn = new Ox.Button({
id: options.id + "InfoBtn",
@ -104,7 +172,7 @@ Ox.ItfBox = function(options, self) {
var $menuBtn = new Ox.Button({
id: options.id + 'MenuBtn',
style: 'symbol',
title: 'expand',
title: 'collapse',
type: 'image',
tooltip: 'Show Menu'
}).appendTo($buttons);
@ -184,9 +252,9 @@ Ox.ItfBox = function(options, self) {
});
}
var $listContainer = new Ox.Element()
var $listContainer = that.$listContainer = new Ox.Element()
.appendTo(that)
.css({position: 'relative', height: '100%'});
.css({position: 'relative', height: '90%'});
var listOptions = $.extend(options, {
'width': 256,
@ -215,7 +283,7 @@ Ox.ItfBox = function(options, self) {
operator: '-',
unique: false,
visible: true,
width: 230
width: 256
},
{
id: 'id',
@ -241,7 +309,11 @@ Ox.ItfBox = function(options, self) {
Ox.ItfList = function(options, self) {
var self = self || {};
var that = new Ox.TextList(options, self);
var opts = $.extend({
'scrollbarVisible': false,
'hasComments': true
}, options);
var that = new Ox.TextList(opts, self);
that.bindEvent("select", function(e, data) {
if (data.ids.length === 0) {
app.$ui.previewBox.$content.html('');
@ -250,8 +322,8 @@ Ox.ItfList = function(options, self) {
app.$ui[options.boxId].$loading.start();
// debugger;
app.api.preview({
model: options.model,
module: options.module,
model: opts.model,
module: opts.module,
id: data.ids[0]
}, function(response) {
// alert(JSON.stringify(response.data.data));
@ -263,14 +335,17 @@ Ox.ItfList = function(options, self) {
});
that.bindEvent("open", function(e, data) {
// alert(JSON.stringify(data));
app.$ui[options.boxId].$loading.start();
app.$ui[opts.boxId].$loading.start();
app.api.info({
model: options.model,
module: options.module,
model: opts.model,
module: opts.module,
id: data.ids[0]
}, function(response) {
app.$ui[options.boxId].$loading.stop();
app.$ui[opts.boxId].$loading.stop();
var html = $.tmpl(response['template'], response['data']);
var content = new Ox.Element().append(html);
var commentForm = ITF.CommentForm(opts, response['commentForm']).appendTo(content);
var comments = _getComments(response['comments']).appendTo(content);
// alert(html);
var d = new Ox.Dialog({
buttons: [
@ -280,7 +355,7 @@ Ox.ItfList = function(options, self) {
})
.bindEvent("click", function() { d.close(); })
],
content: new Ox.Element().append(html),
'content': content,
title: options.title + ": " + response.data.title,
width: 800,
height: 500
@ -292,6 +367,24 @@ Ox.ItfList = function(options, self) {
return that;
}
function _getComments(comments) {
var that = new Ox.Element().addClass("OxCommentsWrapper")
if (comments.length > 0) {
var title = new Ox.Element("h4").html("Comments:").appendTo(that);
for (var i=0; i<comments.length; i++) {
var comment = comments[i];
var $comment = new Ox.Element().addClass("OxCommentWrapper");
var $user = new Ox.Element().addClass("OxCommentsPostedBy").html("posted by " + comment.name).appendTo($comment);
var $commentBody = new Ox.Element().addClass("OxCommentBody").html("comment: " + comment.comment).appendTo($comment);
$comment.appendTo(that);
}
} else {
var $nocomments = new Ox.Element().addClass("OxNoComments").html("No comments on this yet..").appendTo(that);
}
return that;
}
Ox.ItfCalendar = function(options, self) {
var self = self || {};
var that = new Ox.Element(options, self);
@ -310,5 +403,31 @@ Ox.ItfPreview = function(options, self) {
return that;
}
ITF.CommentForm = function(options, formData) {
var that = new Ox.Element();
var title = new Ox.Element("h4").html("Add Comment:").appendTo(that);
var textarea = new Ox.Element("textarea")
.css({'width': '450px', 'height': '200px', 'display': 'block', 'marginBottom': '3px'})
.appendTo(that);
var submitBtn = new Ox.Button({
'id': 'commentFormSubmit',
'title': 'Submit',
'size': 'Large'
}).bindEvent("click", function() {
var data = $.extend({
'id': options.id,
'module': options.module,
'model': options.model,
'name': app.user.username,
'email': app.user.email,
'comment': textarea.val()
}, formData);
app.api.addComment(data, function(response) {
alert(JSON.stringify(response));
});
}).appendTo(that);
return that;
}
ITF.templates = {};

View File

@ -8,6 +8,8 @@ from django.views.generic.simple import direct_to_template
from django.contrib import admin
admin.autodiscover()
from api import actions
actions.autodiscover()
urlpatterns = patterns('',
# Example: