Skip to content

Instantly share code, notes, and snippets.

@virtix
Created November 21, 2011 11:48
Show Gist options
  • Save virtix/1382411 to your computer and use it in GitHub Desktop.
Save virtix/1382411 to your computer and use it in GitHub Desktop.
Trying to do a many-to-may model in django
/**
* @copyright
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Stephane Roucheray
* @see Plugin Page : http://code.google.com/p/jquery-dynamic-form/
* @see Author's Blog : http://sroucheray.org
* @see Follow author : http://twitter.com/sroucheray
* @extends jQuery (requires at version >= 1.4)
* @version 1.0.3
----------------------------------------------------------------------------------------------------------
Changed the way form fields are named. Seems strange the way it was done.
TODO: Look into jquery.format( .... ) and create form segment templates.
*/
(function($){
/**
* @param {String} plusSelector HTML element serving the duplication when clicking on it
* @param {String} minusSelector HTML element deleting the cloned form element
* @param {Object} options Optional object, can contain any of the parameters below :
* limit {Number} : maximum number of duplicate fields authorized
* formPrefix {String} : the prefix used to identify a form (if not defined will use normalized source selector)
* afterClone {Function} : a callback function called as soon as a duplication is done,
* this is useful for custom insertion (you can insert the duplicate anywhere in the DOM),
* inserting specific validation on cloned field
* - this function will be passed the cloned element as a unique parameter
* - return false if the cloned element should not be inserted
* normalizeFullForm {Boolean} : normalize all fields in the form (even outside the template) for better server side script handling (default true)
*
* createColor {String} : color effect when duplicating (requires jQuery UI Effects module)
* removeColor {String} : color effect when removing duplicate (idem)
* duration {Number} : color effect duration (idem)
*
* data {Object} : A JSON based representation of the data which will prefill the form (equivalent of the inject method)
*
*/
$.fn.dynamicForm = function (plusSelector, minusSelector, options){
var source = $(this),
minus,
plus,
template,
formFields = "input, checkbox, select, textarea",
clones = [],
defaults = {
duration:1000,
normalizeFullForm:true,
isSubDynamicForm:false
},
subDynamicForm = [],
formPrefix;
// Set plus and minus elements within sub dynamic form clones
if(options.internalSubDynamicForm){
minus = $(options.internalContainer).find(minusSelector);
plus = $(options.internalContainer).find(plusSelector);
}else{ //Set normal plus an minus element
minus = $(minusSelector);
plus = $(plusSelector);
}
// Extend default options with those provided
options = $.extend(defaults, options);
//Set the form prefix
formPrefix = options.formPrefix || source.selector.replace(/\W/g, "");
/**
* Clone the form template
*/
function cloneTemplate(disableEffect){
var clone, callBackReturn;
clone = template.cloneWithAttribut(true);
if (typeof options.afterClone === "function") {
callBackReturn = options.afterClone(clone);
}
if(callBackReturn || typeof callBackReturn == "undefined"){
clone.insertAfter(clones[clones.length - 1] || source);
}
clone.getSource = function(){
return source;
};
/* Normalize template id attribute */
if (clone.attr("id")) {
clone.attr("id", clone.attr("id") + clones.length);
}
if (clone.effect && options.createColor && !disableEffect) {
clone.effect("highlight", {color:options.createColor}, options.duration);
}
return clone;
}
/**
* On cloning make the form under the clone dynamic
* @param {Object} clone
*/
function dynamiseSubClones(clone){
$(subDynamicForm).each(function(){
var plus = this.getPlusSelector(), minus = this.getMinusSelector(), options = this.getOptions(), selector = this.selector;
clone.find(this.selector).each(function(){
options = $.extend(
{
internalSubDynamicForm:true,
internalContainer:clone,
isInAClone:true,
outerCloneIndex:clones.length,
selector:selector
}, options);
$(this).dynamicForm(plus, minus, options);
});
});
}
/**
* Handle click on plus when plus element is inside the template
* @param {Object} event
*/
function innerClickOnPlus(event, extraParams){
var clone,
currentClone = clones[clones.length -1] || source;
event.preventDefault();
currentClone.find(minusSelector).show();
currentClone.find(plusSelector).hide();
if (clones.length === 0) {
source.find(minusSelector).hide();
}
clone = cloneTemplate(extraParams);
plus = clone.find(plusSelector);
minus = clone.find(minusSelector);
minus.get(0).removableClone = clone;
minus.click(innerClickOnMinus);
if (options.limit && (options.limit - 2) > clones.length) {
plus.show();
minus.show();
}else{
plus.hide();
minus.show();
}
clones.push(clone);
normalizeClone(clone, clones.length);
dynamiseSubClones(clone);
}
/**
* Handle click on plus when plus element is outside the template
* @param {Object} event
*/
function outerClickOnPlus(event, extraParams){
var clone;
event.preventDefault();
/* On first add, normalize source */
if (clones.length === 0) {
minus.show();
}
clone = cloneTemplate(extraParams);
if (options.limit && (options.limit - 3) < clones.length) {
plus.hide();
}
clones.push(clone);
normalizeClone(clone, clones.length);
dynamiseSubClones(clone);
}
/**
* Handle click on minus when minus element is inside the template
* @param {Object} event
*/
function innerClickOnMinus(event){
event.preventDefault();
if (this.removableClone.effect && options.removeColor) {
that = this;
this.removableClone.effect("highlight", {
color: options.removeColor
}, options.duration, function(){that.removableClone.remove();});
} else {
this.removableClone.remove();
}
clones.splice($.inArray(this.removableClone, clones),1);
if (clones.length === 0){
source.find(plusSelector).show();
}else{
clones[clones.length -1].find(plusSelector).show();
}
}
/**
* Handle click on minus when minus element is outside the template
* @param {Object} event
*/
function outerClickOnMinus(event){
event.preventDefault();
var clone = clones.pop();
if (clones.length >= 0) {
if (clone.effect && options.removeColor) {
that = this;
clone.effect("highlight", {
color: options.removeColor, mode:"hide"
}, options.duration, function(){clone.remove();});
} else {
clone.remove();
}
}
if (clones.length === 0) {
minus.hide();
}
plus.show();
}
/**
* Normalize ids and name attributes of all children forms fields of an element
* @param {Object} elmnt
*/
function normalizeSource(elmnt, prefix, index){
elmnt.find(formFields).each(function(){
var that = $(this),
nameAttr = that.attr("name"),
origNameAttr = that.attr("origname"),
idAttr = that.attr("id"),
origId = that.attr("origid");
/* Normalize field name attributes */
if (nameAttr) {
//TODO:
that.attr("name", nameAttr + '_' + index);
console.log(that)
}
//if (!/\[\]$/.exec(nameAttr)) that.attr("name", nameAttr + index);
else if(origNameAttr){
//This is a subform (thus prefix is not the same as below)
that.attr("name", prefix+"["+index+"]"+"["+origNameAttr+"]");
}else{
//This is the main form
that.attr("origname", nameAttr);
//This is the main normalization
that.attr("name", prefix+"["+index+"]"+"["+nameAttr+"]");
}
/* Normalize field id attributes */
if (idAttr) {
/* Normalize attached label */
that.attr("origid", idAttr);
$("label[for='"+idAttr+"']").each(function(){
$(this).attr("origfor", idAttr);
$(this).attr("for", idAttr + index);
});
that.attr("id", idAttr + index);
}
});
}
function normalizeClone(elmnt, index){
var match, matchRegEx = /(.+\[[^\[\]]+\]\[)(\d+)(\]\[[^\[\]]+\])$/;
elmnt.find(formFields).each(function(){
var that = $(this),
nameAttr = that.attr("name"),
origNameAttr = that.attr("origname"),
idAttr = that.attr("id"),
counter = index;
match = matchRegEx.exec(nameAttr);
//that.attr("name", match[1]+index+match[3]);
if (nameAttr) {
//TODO:
that.attr("name", nameAttr.replace(/[0-9]/, counter++) );
console.log (that)
}
else if (idAttr) {
newIdAttr = idAttr.slice(0,-1) + index;
that.attr("origid", idAttr);
elmnt.find("label[for='"+idAttr+"']").each(function(){
$(this).attr("for", newIdAttr);
});
that.attr("id", newIdAttr);
}
});
}
function normalizeSubClone(elmnt, formPrefix, index){
var match, matchRegEx = /(.+)\[([^\[\]]+)\]$/;
elmnt.find(formFields).each(function(){
var that = $(this),
nameAttr = that.attr("name"),
idAttr = that.attr("id"),
newIdAttr = idAttr + index,
match = matchRegEx.exec(nameAttr);
that.attr("name", match[1]+"["+formPrefix+"]"+"["+index+"]"+"["+match[2]+"]");
if (idAttr) {
that.attr("origid", idAttr);
elmnt.find("label[for='"+idAttr+"']").each(function(){
$(this).attr("for", newIdAttr);
});
that.attr("id", newIdAttr);
}
});
}
//Add a function to enable sub dynamic forms to register themselves
source.each(function(){
$.extend(this, {
addSubDynamicForm : function(dynamicForm){
subDynamicForm.push(dynamicForm);
},
getFormPrefix : function(){
return formPrefix;
},
getSource : function(){
return source;
}
});
});
//Check if this dynamic form is a sub dynamic form
var isMainForm = true;
$(this).parentsUntil("body").each(function(){
if($.isFunction(this.addSubDynamicForm) && !options.isSubDynamicForm){
isMainForm = false;
options.isSubDynamicForm = true;
var suboptions = $.extend(
{
internalSubDynamicForm:true,
internalContainer:this
}, options);
this.addSubDynamicForm(source);
formPrefix = this.getFormPrefix()+"[0]["+formPrefix+"]";
return false;
}
});
if(isMainForm && !options.isInAClone){
//Main form name and prefix for the main form are the same for now
formPrefix = formPrefix+"["+formPrefix+"]";
}
if(!options.isInAClone){
normalizeSource(source, formPrefix, 0);
}else{
formPrefix = formPrefix || options.selector.replace(/\W/g, "");
//Main form name and prefix for the main form are the same for now
normalizeSubClone(source, formPrefix, 0);
}
if(isMainForm && options.normalizeFullForm && !options.isInAClone){
//Normalize all forms outside duplicated template in order to ease server-side parsing
$(this).parentsUntil("form").each(function(){
var theForm = $(this).parent().get(0);
$(theForm).find(formFields).filter("[type!=submit]").each(function(){
var that = $(this),
nameAttr = that.attr("name"),
origNameAttr = that.attr("origname"),
idAttr = that.attr("id"),
origId = that.attr("origid");
if(!origNameAttr){
// Normalize field name attributes
if (!nameAttr) {
//TODO: that.attr("name", formPrefix+"form"+index + "["+index+"]");
that.attr("name", prefix+"["+index+"]"+"["+origNameAttr+"]");
}
//It's the main form
that.attr("origname", nameAttr);
//This is the main normalization
that.attr("name", formPrefix+"["+nameAttr+"]");
}
});
});
}
isPlusDescendentOfTemplate = source.find("*").filter(function(){
return this == plus.get(0);
});
isPlusDescendentOfTemplate = isPlusDescendentOfTemplate.length > 0 ? true : false;
/* Hide minus element */
minus.hide();
/* If plus element is within the template */
if (isPlusDescendentOfTemplate) {
/* Handle click on plus */
plus.click(innerClickOnPlus);
}else{
/* If plus element is out of the template */
/* Handle click on plus */
plus.click(outerClickOnPlus);
/* Handle click on minus */
minus.click(outerClickOnMinus);
}
$.extend( source, {
getPlus : function(){
return plus;
},
getPlusSelector : function(){
return plusSelector;
},
getMinus : function(){
return minus;
},
getMinusSelector : function(){
return minusSelector;
},
getOptions : function(){
return options;
},
getClones : function(){
var clonesAndSource = [source];
return clonesAndSource.concat(clones);
},
getSource : function(){
return source;
},
inject : function(data){
/**
* Fill data of each main dynamic form clones
* @param {Object} formIndex
* @param {Object} formValue
*/
function fillData(formIndex, formValue){
//Loop over data form array (each item will match a specific clone)
var mainForm = this;
//Shows required additional dynamic forms
if(formIndex > 0){
mainForm.getSource().getPlus().trigger("click", ["disableEffect"]);
}
var clone = mainForm.get(0).getSource().getClones()[formIndex];
$.each(formValue, function(index, value){
if($.isArray(value)){
mainForm = clone.find("#"+index);
if(typeof mainForm.get(0).getSource === "function"){
$.each(value, $.proxy( fillData, mainForm.get(0).getSource()));
}
}else{
var formElements = mainForm.getSource().getClones()[formIndex].find("[origname='"+index+"']");
if(formElements){
if(formElements.get(0).tagName.toLowerCase() == "input"){
/* Fill in radio input */
if(formElements.attr("type") == "radio"){
formElements.filter("[value='"+value+"']").attr("checked", "checked");
}else if(formElements.attr("type") == "checkbox"){/* Fill in checkbox input */
formElements.attr("checked", "checked");
}else{
formElements.attr("value", value);
}
}else if(formElements.get(0).tagName.toLowerCase() == "textarea"){
/* Fill in textarea */
formElements.text(value);
}else if(formElements.get(0).tagName.toLowerCase() == "select"){
/* Fill in select */
$(formElements.get(0)).find("option").each(function(){
if($(this).text() == value || $(this).attr("value") == value){
$(this).attr("selected", "selected");
}
});
}
}
}
});
}
//Loop over each form
$.each(data, $.proxy( fillData, source ));
}
});
template = source.cloneWithAttribut(true);
if(options.data){
source.inject(options.data);
}
return source;
};
/**
* jQuery original clone method decorated in order to fix an IE < 8 issue
* where attributs especially name are not copied
*/
jQuery.fn.cloneWithAttribut = function( withDataAndEvents ){
if ( jQuery.support.noCloneEvent ){
return $(this).clone(withDataAndEvents);
}else{
$(this).find("*").each(function(){
$(this).data("name", $(this).attr("name"));
});
var clone = $(this).clone(withDataAndEvents);
clone.find("*").each(function(){
$(this).attr("name", $(this).data("name"));
});
return clone;
}
};
})(jQuery);
from django.db import models
from Crypto.Cipher import AES
class Mode(models.Model):
mode_name = models.CharField(max_length=56)
def __unicode__(self):
return u'%s' % self.mode_name
class TransitMode(models.Model):
mode = models.ForeignKey(to='Mode')
transit =models.ForeignKey(to='TransitBenefit')
cost = models.IntegerField()
class TransitBenefit(models.Model):
name = models.CharField(max_length=56)
email = models.EmailField()
amount = models.IntegerField()
modes = models.ManyToManyField(to=Mode,through='TransitMode')
def __unicode__(self):
return u'%s %s <%s>' % (self.first_name, self.last_name,self.email)
"""
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
from django.http import QueryDict
import views
from models import Mode,TransitBenefit,TransitMode
class TransitModelTests(TestCase):
fixtures = ['transit_modex.json']
def test_build_simple_transit_benefit(self):
t = TransitBenefit()
t.name ='bill'
t.email='bill@if.io'
t.amount=100
m1 = Mode.objects.create('taxi')
_modes = Mode.objects.create( mode=m1,transit=t,cost=50 )
class TransitViewTests(TestCase):
def test_that_segments_are_parsed(self):
request = QueryDict('')
query_string = 'segment_type_0=dash&segment_cost_0=1&segment_type_1=metro&segment_cost_1=3&segment_type_2=vre&segment_cost_2=5&segment_type_3=eerie&segment_cost_3=7&segment_type_4=dillon&segment_cost_4=9'
request.POST = QueryDict(query_string)
actual = views.get_segments(request)
self.assertEqual( u'1', actual['dash'])
self.assertEqual( u'3', actual['metro'])
self.assertEqual( u'5', actual['vre'])
self.assertEqual( u'7', actual['eerie'])
self.assertEqual( u'9', actual['dillon'])
print actual
def test_that_disparate_segments_are_ignored(self):
request = QueryDict('')
query_string = 'foo=bar&asd=asd&segment_type_0=metro&segment_cost_0=180&x=z'
request.POST = QueryDict(query_string)
actual = views.get_segments(request)
self.assertEqual( u'180', actual['metro'])
print actual
from django.utils.unittest.case import skipIf
from django.db.models import Avg, Max, Min, Count, Sum
from decimal import *
from datetime import datetime
from django.test import TestCase
from front.models import OfficeLocation
from django.contrib.auth.models import User
from transit_subsidy.models import TransitSubsidy,TransitSubsidyForm,Mode,TransitSubsidyModes
from front.models import Person
class TransportationSubsidyMany2ManyTest(TestCase):
fixtures = ['offices.json','transit_modes.json']
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def setUp(self):
"""
Assumes valid user
"""
self.user = User.objects.create_user('test_user','test_user@user.name','password')
is_logged_in = self.client.login(username='test_user',password='password')
self.assertTrue(is_logged_in, 'Client not able to login?! Check fixture data or User creation method in setUp.')
self.office = OfficeLocation.objects.order_by('city')[0]
self.modes = Mode.objects.all()
def tearDown(self):
pass
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# --- To Do: Refactor to model_form_tests
def test_create_simple_transit_subsidy(self):
trans = self._set_transit_subsidy()
trans.save()
#Metro
_modes = TransitSubsidyModes(transit_subsidy=trans, mode=self.modes[0], cost=100)
_modes.save()
#Dash
_modes = TransitSubsidyModes(transit_subsidy=trans, mode=self.modes[1], cost=50)
_modes.save()
ts_modes = TransitSubsidyModes.objects.all()
actual = ts_modes.aggregate( Sum('cost') )
print actual['cost__sum']
self.assertEquals( Decimal('150.00'), actual['cost__sum'] )
def _set_transit_subsidy(self):
transit = TransitSubsidy()
office = OfficeLocation.objects.order_by('city')[0]
transit.user = self.user
transit.destination = office
transit.date_enrolled = '2011-06-23'
transit.origin_street = '123 Main Street'
transit.origin_city = 'Anytown'
transit.origin_state = 'VA'
transit.origin_zip = '22222'
transit.route_description = "Harbor bus to City Subway stop to Work Subway"
transit.number_of_workdays = 20
transit.daily_roundtrip_cost = 8
transit.daily_parking_cost = 4
transit.amount = 8
transit.dc_wmta_smartrip_id = '123-123-123'
transit.terms_of_service = True
transit.supervisor_approval = True
return transit
# transit.save()
from django.template import RequestContext, loader
from django.shortcuts import render_to_response
from django.core import serializers
from django.http import HttpResponse
from smartrip.models import *
from dynamicresponse.response import *
from django.views.decorators.csrf import csrf_exempt
from models import TransitBenefit
def get_segments(request):
segments = {}
for name in request.POST:
if name.startswith('segment_type'):
name_key = request.POST[name]
cost_key = name.replace('type', 'cost')
segments[name_key] = request.POST[cost_key]
return segments
@csrf_exempt
def ajax(request):
if request.method == 'POST':
segments = get_segments(request)
# Save model then segments?
return HttpResponse('ok-Django says, got request. %s' % str(request.raw_post_data) ) #segments
else:
render_to_response('index.html')
def home(request):
tb = TransitBenefit()
tb.first_name = 'ed'
tb.last_name = 'last name'
tb.email = 'ed@ed.com'
tb.last_four_ssn = '1234'
tb.amount = 200
tb.date_requested = '2011-09-07'
#tb.save()
return render_to_response('index.html')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment