From a03e46d83573b06ce0944036057880a7ba3238fe Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Tue, 4 Mar 2014 15:15:16 -0500 Subject: [PATCH 01/24] scaffolding for manager utils --- LICENSE | 23 ++- MANIFEST.in | 3 + README.md | 66 +++++++- manage.py | 10 ++ manager_utils/.manager_utils.py.swp | Bin 0 -> 16384 bytes manager_utils/VERSION | 1 + manager_utils/__init__.py | 1 + manager_utils/__init__.pyc | Bin 0 -> 210 bytes manager_utils/fields.py | 56 +++++++ manager_utils/fields.pyc | Bin 0 -> 2346 bytes manager_utils/import_string.py | 19 +++ manager_utils/import_string.pyc | Bin 0 -> 886 bytes manager_utils/manager_utils.py | 96 ++++++++++++ requirements.txt | 9 ++ setup.py | 29 ++++ test_project/__init__.py | 0 test_project/__init__.pyc | Bin 0 -> 148 bytes test_project/models.py | 15 ++ test_project/models.pyc | Bin 0 -> 1616 bytes test_project/settings.py | 161 ++++++++++++++++++++ test_project/settings.pyc | Bin 0 -> 2835 bytes test_project/tests/__init__.py | 0 test_project/tests/__init__.pyc | Bin 0 -> 154 bytes test_project/tests/callable_field_tests.pyc | Bin 0 -> 9510 bytes test_project/tests/manager_utils_tests.py | 0 25 files changed, 474 insertions(+), 15 deletions(-) create mode 100644 MANIFEST.in create mode 100644 manage.py create mode 100644 manager_utils/.manager_utils.py.swp create mode 100644 manager_utils/VERSION create mode 100644 manager_utils/__init__.py create mode 100644 manager_utils/__init__.pyc create mode 100644 manager_utils/fields.py create mode 100644 manager_utils/fields.pyc create mode 100644 manager_utils/import_string.py create mode 100644 manager_utils/import_string.pyc create mode 100644 manager_utils/manager_utils.py create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 test_project/__init__.py create mode 100644 test_project/__init__.pyc create mode 100644 test_project/models.py create mode 100644 test_project/models.pyc create mode 100644 test_project/settings.py create mode 100644 test_project/settings.pyc create mode 100644 test_project/tests/__init__.py create mode 100644 test_project/tests/__init__.pyc create mode 100644 test_project/tests/callable_field_tests.pyc create mode 100644 test_project/tests/manager_utils_tests.py diff --git a/LICENSE b/LICENSE index a466289..9875435 100644 --- a/LICENSE +++ b/LICENSE @@ -2,20 +2,19 @@ The MIT License (MIT) Copyright (c) 2014 Ambition -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..57acd2a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include manager_utils/VERSION +include README.md +include LICENSE diff --git a/README.md b/README.md index 60fee80..b0b1a4d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,64 @@ -django-manager-utils -==================== +[![Build Status](https://travis-ci.org/ambitioninc/django-callable-field.png)](https://travis-ci.org/ambitioninc/django-callable-field) +django-callable-field +===================== -Model manager utils for Django +Store callable functions/classes in Django models. + +## A Brief Overview +The Django callable field app provides a custom field for a Django model that stores a callable function or class. This provides the ability to easily store paths to code in a model field and quickly execute it without having to load it manually. + + +## Storing and Calling a Function +Assume that you have defined the following function that returns the argument that you passed to it: + + def ret_arg(arg): + return arg + +In order to save this callable function to a model, simply do the following: + + from django.db import models + from callable_field import CallableField + + class CallableModel(models.Model): + my_function = CallableField(max_length=128) + + model_obj = CallableModel.objects.create(my_function='full_path_to_function.ret_arg') + # Call the function from the my_function field and print the results + print model_obj.my_function('Hello World') + Hello World + +You can similarly pass a function variable (instead of a string) to the model constructor: + + model_obj = CallableModel.objects.create(my_function=ret_arg) + print model_obj.my_function('Hello World') + Hello World + + +## Storing and Calling a Class +Similar to the function example, assume that you have defined the following class that returns the argument passes to its constructor: + + class RetArg(object): + def __init__(arg): + self.arg = arg + + def ret_arg(): + return self.arg + +Similar to the function example, do the following to save the class: + + from django.db import models + from callable_field import CallableField + + class CallableModel(models.Model): + my_class = CallableField(max_length=128) + + model_obj = CallableModel.objects.create(my_class='full_path_to_class.RetArg') + # Instantiate the class from the my_class field and print the results + print model_obj.my_class('Hello World').ret_arg() + Hello World + +You can similarly pass a class variable (instead of a string) to the model constructor: + + model_obj = CallableModel.objects.create(my_class=RetArg) + print model_obj.my_class('Hello World').ret_arg() + Hello World diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..1013983 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == '__main__': + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings') + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/manager_utils/.manager_utils.py.swp b/manager_utils/.manager_utils.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..e54ecb4ae0a759a9bf71f34950a0a71d426d0efe GIT binary patch literal 16384 zcmeHNONbmr7%r3eHX1dGh(eh~G8<+)F%iYUD3L_TAyH%Wpvc%WT{F|!p6*Fk^=#(i z`=B7=DF!_#@t}wZDu_?aK?PB7K2PG~BpwsU!GqslkM5pvT_s*5RKvHuJze$J|9}6Z zs=B+g<=)+U_>I9fhGQLL`?p zSjAboFpOn)b95j~``N-UOvAp&7YY?8aun;2AjG7*L~C>|q$S${KcuJ)bdi#v=iJ zTx`#G*|TeBFa60g;2H1?cm_NJo&nE*XTUSy8So5v20R0vf&U=`VZ_*L*xau=6UyQJ zzuW)+dJ$v403QKIfaie2zz7%s>wyb^pDtwV8{h@tdEhDFCSU>Jz=^eteFeM&ybK%# zo(3KPt^j_%fU$3Zw}H2SH-Tq?CxL5$Kh9_DIPg92D)0)B0v+IL;40uk;Is1>>jC!x zJAfa~g)HC|;0fR$Faq`hTY>e!kLNIU4EPv$3>X7*z-7SuXEU}B*aUoj7GnC1cz&>Czuo@s9e_O-Y@4z#_qrlBT3upi*!1Zz9d*D0Zb>JnS2MAz4um(7Z z2Qu#g?*d1G!$1I>!~>V#fn)Z;3%5ruRFS7#ili6xg=!xxL_U#1HRb~ur~O1U=9z1| zVZ(;DhGo~43kIZrb_4wFJOIers|x?)5hYw%d|JjC}MQBd~UA!RWXlB2i6AG8=| zkw_pYneagR|UlE*6GyL+{p)N`2Sq8!I*8i^jaW{`#R z+Ywy$Six}OAT#cH6{)v~ye z)#Q6Mf3PuM0!n*Iumufh>j+o zv930v9RMRXc{dlK5>dq+8H_u;WsGij6B!uYBn44pwAjLBFj}nmqAqiMpGY#Q9CYOl z9RX7!dU2veUXv0OqX-(n8E|M=B&y~J62qJ~rN|Y@{5xGOl{2b_ zxv(T#-Ky?X<4mV0Ap0gfOk~EhvB-0>E=(tX(X}JU`#dUgQbp5_YB^0BS*B^I4HYIx z!)RiNkTe_y*Kx+>AS)6IUQ|(tuUNFa=o_g3w`#}TxQ#iZk`3K-CkD556s6sSp{ zCeCwI>@j`tM{3it1JP2&=$#^m6Nkt}7tEAqQ{@a7Mlc0*&1NcUI;vNwYQ>1$+Asyp z8PuZ6=+a!;D;J9OX<**f7VE}#o-NfPovg9B+#Q5*+GuQyqm9k^rv0_8vbO0t-(=L- z9eUPkRq0mUy^Cg8Os~B>8@6?`26MgU_^v~uTPTwkog!JZ;Wb?{O>r)JfwM%Uw)8~C z0^VxzEtL-*C>zsIlvAecB4a68$_qKw&YE1_4dRGQZ=lBO>Q&ybbhOiODCV_zO`5(Y z`G&$8Syj2K^NzZDf2qK1k(rgT9dmAixeoQf%13nT@YjoE+dl?$t5_=BegY?;{l38ge84GW|AkY37R$=5}qy8_jjaPuXvVV>JBcv0DQD>prW zwJ6e`{)P$f6=|0imSGaBiJqymk*1n-(j&U@wRwAJSejrlB~iwgUa?eYt7)|oN9}fB ziPn}XD4mrY$LX@t1rsF&WEZ7=OD`A7pUv9cYzA~-1v^oI_hxDrEh?*OQgts$oLNbh zB2_-A`qgPA8K=pjnM`q}?G(K@)jJ%)he8c9EG>2Pun;2H1?cm_NJ|AGP9 zuIXduK|uTOe%CuFdJgiR-ib{~pSn1;*KbaQdlzK3;_la3+g7sSRQkC6F7-WKo{+eQ X8P$WYdHqh*zYEmwi1EOvdKdU7G8n`9 literal 0 HcmV?d00001 diff --git a/manager_utils/VERSION b/manager_utils/VERSION new file mode 100644 index 0000000..d917d3e --- /dev/null +++ b/manager_utils/VERSION @@ -0,0 +1 @@ +0.1.2 diff --git a/manager_utils/__init__.py b/manager_utils/__init__.py new file mode 100644 index 0000000..8a5d527 --- /dev/null +++ b/manager_utils/__init__.py @@ -0,0 +1 @@ +from .fields import CallableField diff --git a/manager_utils/__init__.pyc b/manager_utils/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2dd1b4ee831d414ca592ccb12beed9df22ab02f GIT binary patch literal 210 zcmZSn%*z!~E)<;100m4y+5w1*MSw&K5HT|3FfimYGDI;lFs3ju1Zyw@B{CU-ga(Kx z;RO=Ti8(omNja%*nW;G`ei|S%O4xuz8i-#U1Y|L2pc2LQK)!xrZc=7RW`3S-Zf0?^ zeo9tiUV6T6GSo0#ux@=AHy*-`kI&4@EQycTE2u2t0GeTwo1apelWGTYOEJg}04ahj A@c;k- literal 0 HcmV?d00001 diff --git a/manager_utils/fields.py b/manager_utils/fields.py new file mode 100644 index 0000000..e5a6427 --- /dev/null +++ b/manager_utils/fields.py @@ -0,0 +1,56 @@ +from django.db.models import SubfieldBase +from django.db.models.fields import CharField + +from callable_field.import_string import import_string + + +class CallableField(CharField): + """ + A field that loads a python path to a function or class. + """ + description = 'A loadable path to a function or class' + __metaclass__ = SubfieldBase + # A cache of loaded callables for quicker lookup + imported_module_cache = {} + + def get_prep_value(self, value): + value = self.to_python(value) + return self.value_to_string(value) + + def get_imported_module(self, path): + if path not in self.imported_module_cache: + self.imported_module_cache[path] = import_string(path) + return self.imported_module_cache[path] + + def to_python(self, value): + """ + Handles the following cases: + 1. If the value is already the proper type (a callable), return it. + 2. If the value is a string, import it and return the callable. + + Raises: A ValidationError if the string cannot be imported. + """ + if callable(value): + return value + else: + if value == '' and self.blank: + return '' + elif value is None and self.null: + return None + else: + return self.get_imported_module(value) + + def value_to_string(self, obj): + if type(obj) in (str, unicode): + return obj + elif obj is None: + return None + else: + return '{0}.{1}'.format(obj.__module__, obj.__name__) + + +try: + from south.modelsinspector import add_introspection_rules + add_introspection_rules([], ['^callable_field\.fields\.CallableField']) +except ImportError: + pass diff --git a/manager_utils/fields.pyc b/manager_utils/fields.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54f6cd2aeaa6de9d434edf2269f8c98008078eec GIT binary patch literal 2346 zcmb_dO>-PI5N*xuH!)d4Vo2eG4nR!`d!2Bk0)=fzk&6$rReWrvP-$nh-m$eaTaq@E zEBHeAZ~P#B0Ccx@*Kwe@;8{zST2iau(^D(`b7%1Kk9*?+)?XX{e~rcehE<7w0xZBn zz(jzcegZauMGBKN_EWGeShQi%#(oP-3Nry^8-53P4e-;X1D7AOqYl_EEP62MfiOKd zLtkAu6Yx3#@k9L{v`il^2H>th2vH08vFyP(>th(zU-)GLTj35?>O8G77>oY~D+2@-kxmiI!fNkFyx4z=ZjN{vMgCt4iUg-_$_a(Aht;nD=U5}r0b}+$Rt= z__olW)^G6e@CNJ$xIPNSlxoqGD{EAti@C{(6~>&?aRv&3PU}|TzK>1`?pqPuN3*A0 z3G?nXYKnnTkFgOpr|O2pz!(b(_96lIXno4qKno8faM1&I%mXPkP|0KBr?t@n88V{P zckx27_DHAJMWqj-&+HP$x2;AN5p-aY@tcG8oPd_LnTd}Ojhgxh9`!e!`^CgD0SxupBe2xa&pmJSNkn%`= zuWeOo(&V?!;q|IwtkEeht?MSpsabnB<#jN~F~Dg?S`R&}HrufJr&iY&tc%m8Hr%LJ z*5>cv#B1T?UFu@FtviyF3LawdbXlNxN3@a$q9w2-|GXd1aLQIGcr!u*GZaGy+}>6- zz~b1atT#r%L`|HxY5crH33^N;4C&MpoL7&pMz5Y+Wz=TebhLrR6I>wcs;-KrG&$vw zKuVr9ZlOaMU{h`GnPN_rs`bJsbxj$2YNqG6${L;FXIMOi0OCVrjF`*lwDMi5C>3z< zN|jBaRG^{K_`+375>Jj@;CBQOn4mdJE0t5`%t;-5@_alw?RFhLxBh>2%!y%Q*HcpL zq#c33q?7bp{dWIeeE2@V5nYz5s)K9%((s$29HuqTPsz_43CGm?Via@li_yuOZ{ul3 zECt#oCh$=?9btAf)<+g-o3}^Su5x~<_i)0^twuNAV@|IOC=l<+~+8K%1jsThj;Vn_T9-X0)! literal 0 HcmV?d00001 diff --git a/manager_utils/import_string.py b/manager_utils/import_string.py new file mode 100644 index 0000000..63afca4 --- /dev/null +++ b/manager_utils/import_string.py @@ -0,0 +1,19 @@ +from django.core.exceptions import ValidationError + + +def import_string(module_string): + """ + Imports a string as a python object. Returns None if the module could not be + imported. + """ + try: + path = '.'.join(module_string.split('.')[:-1]) + module_name = module_string.split('.')[-1] + file_name = module_string.split('.')[-2] + + module_path = __import__(path, globals(), locals(), [file_name]) + module = getattr(module_path, module_name) + except: + raise ValidationError('Could not import module {0}'.format(module_string)) + + return module diff --git a/manager_utils/import_string.pyc b/manager_utils/import_string.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3003a3b3c2530555f8c27391da68dc954da307a GIT binary patch literal 886 zcmb_b%Wl&^6uo0RZ3(JCt%MX&&@8=)!v`RQR9LX-B9#DjV@>QyGSS!*d2S>s>=E`kw*LmW_JMUn)Dmq(u=l`-ByOGf zH#+H}`=OX7T}+IqAdYE|qmzJbOe;_&)Fs3tY7=TB+DFul=tF%*LI^&fGNH|3;KNzC zz6h!XeQZZ)CusEOn8p(8UA1)$d#R-7#?&jRg1x=wwX3CDY;?hyoNL}SwU-yJ*3v8` zueGdPxwBdpZf8qbJC+OG6Plh-ms#&|m`v7B&-}y){Zshof1VkNZ@E6d=+YzP(I+P6 zOpW%Sb!!dt7*?JS^?A;Ra97qXl=bMA$hsn!eOR+%Zsv$mpIf@7Qk;e|J_ZcR^P%7V ztgYfTKfFuTs?z)b{?b^T_ugOGzB;fCL@6xl!M}kuQ`N$RFJoF6U(Cvls#k6bbyh8` zo-U2H=1.6', + ], + include_package_data=True, +) diff --git a/test_project/__init__.py b/test_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_project/__init__.pyc b/test_project/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac65e29e8b42417db53e66b5a288cc3412b02f44 GIT binary patch literal 148 zcmZSn%*z$&Efk#000oRd+5w1*S%5?e14FO|NW@PANHCxg#kN2({lwg)%#zIfJl)*P z;$;1lti-(ZeBI>4oSej@oK)Sk%+#C|{gTw;lK6t6{H)aE68-r2%)HE!_;|g7$`THs T2{yU;DWy57b|8C-ftUdRz>Og0 literal 0 HcmV?d00001 diff --git a/test_project/models.py b/test_project/models.py new file mode 100644 index 0000000..7aaaa47 --- /dev/null +++ b/test_project/models.py @@ -0,0 +1,15 @@ +from django.db import models + + +class ForeignKeyTestModel(models.Model): + value = models.CharField(max_length=128) + + +class TestModel(models.Model): + """ + A model for testing manager utils. + """ + int_field = models.IntegerField() + char_field = models.CharField(max_length=128) + float_field = models.FloatField() + foreign_key = models.ForeignKey(ForeignKeyTestModel) diff --git a/test_project/models.pyc b/test_project/models.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf758e2058f431cd673fcb5df05218c85d20525c GIT binary patch literal 1616 zcmb`HOK;Oa5XZ-MUQH>g5Eq1yEOGHAiQo!F(28=RhYc5Uu^jIvaah|?b|ab_QaJE^ z`2aBgb)KR~h{lP>JD&ACemlFt&$IS?@~EHE`U?2@mT&Weh2+0Snke^Z;E@O2C+$-n z&>&zvAm!1tMk9}8jlL7Dh&~VMBLKiX|mGjUlW3yr$>N1gdCWQ>utx6Y&7nY-NwzLx-jn*j` z=fSqwSDxI)S`}kE$-c1~)mhjUi#RTlT*a}Q6~`QGp`kXNO_pgK3s?OqzsQ&I#&^Uj z?Sy&7YA?x$8FK05nMr$cniS)*ldchUMqt!K5%FwZPE~4q70&K#DSZAOfK3g9py9cM zu5@C|{YbuNfB#J~MSN9p6j+!YJ%L08I11!lK>|^Rsw`wp1 zb5r&ZIVN`@!aI1f>xSC2$FXst>mT#I!v%OZ3$2eu>jnF}i&nug{tgTEW5z$Q5;%Ya z(mpmEbk0-0w{UKv6ILxmwQaj{C|Dtjshc&5-4?D|9gFUI&TUvPja5MRfM^A05R0ko9zNb4lE3oPfhiYWs*-?-s<2xmU zf4l!c_g2R7FtpPzoqnh_y1RY*@4=HP&pfz21wLNQfvS_HO)(=r?cOTbnjSO&HV!3wZ7 z2v#Y|8jb7Ze*@S$1XpOhO5-N54G6Y?J%->KuqP0F25b|8&w(94@CC4^5PS*j1_alE z-Gtx^E_6I^p}@VSs)9!u`#Y0Agq=2S;xU9B7S-td$OO}tG|XJ1xOY&uQLF=@$D8OsYf zk6cG@nY8G5rYo0_bF6#b>$YpiWh9cU7bPc|kfd|e_I$aBY{vUho=927b;oZARS!I^ zRWbaSkjJBU|hrf^Sak_=h2vB zT86_c)fzOlrhM`EGQc!*T!=VJg*IdE6}%b4zX&77eXW7RuQ5NL(f&lDqg&h$^mhh3YDly z+bL+6YJ&4&gSAU>FH4D8Z{Bh2y$PM#lr%5gDo2_G8_sy_iDKEb1`Jvl!9K5Vl&Zz^b^}Ke+ zKMIxH3tLvxMLW9dbfaZC9@S+_cl4&|dekI_?&~jg&nzh4YMEis#`|#{z1q6d?4WAc zXd5Qgyk{W{%OE#+Q-;>VXU>$cA3ks>mXi2^RC{ijhNXvYyX{l7g6z09b+6}Re>4iI zQl8LCteUDI4=vmDLc>D5>S&o|9$Jo}_CdC0!!`ZzmH8XlPIG3rb<){VU@XJ1&DXk% zJF@Y5$4#pbq0ogGw9$4B$@sO`v_k#(*tUuWresV#5)^f!U_O1_woOCndXyi#-Nb(v zP0B(;ZycH4Oz)!!h@Ww&N5gKcE|1zU7QK3LGSxnu$4X^9nVj7BnxeHLFxW! zn6W(Jzo4oSej@oK)Sk%+#C|{gTw;lK6t6{H)aE5-_J&KR!M)FS8^*Uaz3C Wgac@fO>TZlX-=vg$hKl2W&i-LbR$jx literal 0 HcmV?d00001 diff --git a/test_project/tests/callable_field_tests.pyc b/test_project/tests/callable_field_tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccdee77b725d24842aef2881c61b60729e141afd GIT binary patch literal 9510 zcmd5?TXP&o74F%qb}dVCj6)2DOa%-J#1i*wph8rnfC@`xY)G;%Q=^@cG^^PQ)3Y|B z?3dWZrHU#SZ@lrs3&jJ!fM=fao+^F@KLEb(^xRgSl`P6@Mbh^6^z?N1Ip;gyIo+fB ze=c|b`mYym4^{e8$M@^_=)a-x@n@+>sj;O7ma=$VQ&CNg>uOLp*L4*&)VQezO>^B) zQA>^6YS32WH8ogM+9Ea#{In%I~O0m}x`#T@`Gq zeM|Y5c=w@F?+vz;zpjF&v|i3zuPA>*-R^B-wc&s9)3Z=ZnkZ&oGCVYwy!%Tp3VkmL zr;{JW@iaC!)^Ouapp#us2fYTLyo}<_`DDnBKb!hNlyuRu8+lrr%MDz<5qXoNJMla) zO?;+5kD|OK*CrRUi;tGE6&A`C{T7O@jh`Rd`{`VEa_A*?sBKIcB!O>-6MH%d>;$W` zy@`*D=-8fX+}S%eoq20phB3l$4lbdGZ&eif=eXSQ#(N=Ux-|~)}cdmp%0M>^PwoWTkxEr4Svo=o$*uI7|H3(5X`V zI7sI4MB9|Y-k-)IeK$EXSA*Iw-A}Xzmg}Dh9C6aETA(NEm%s#oH z@8^d8E^ePHX_#mml8I@&$oArc`8b$lX2sLW*tU)uZWi*cMAUW6LQyf3NcwSHr;>)K zs$nK7zbnN37Mjl#_b52NH;w(}veJ2v?yX*0Vwlp(Tq5tM(UQt5MDW(s^9gWq{~Xl> z`5Wx{DvAJX2AV=yBUgeR$zXNm*M-&^%5O-eshrzAN~XVoB306mv|^n4-eEAw;U1;) z+R`C|y)glizzG#;vA&WV*Z;uZrxPpsaosO)a;`vG9qUO<(Xk=TDD$GexPYwG)JW$m_5`lsz<9zfR1GMeeyyTt zI*Icf)=K8(S_Q{nE>_!Xp`IAUPrPy9x)RG=cRcmyU~M;{l&EFWc1qq6zAOj>Q>(a78Pi9cizd#|L z%q41DC)1e0iqV>Jll3gTBa+hB18qXL7YEYa#Cb+l8%7DDG{A{Z&_QTH`m6psibSbD zDD^uf8IYxZtJDKaCBiw76%_E$vL3&T91F5H(=6>lO<8bu(85FHZ|@-Mdiz~uN28V+ zK@Ip@Lvm$3z~gYZHFdwPMoSc=70+ik6y>6CPI|84kFbI1g^U`52GZU zxb8OSRu%+D=aV}012@>;hm@sEna_&}bYG}qeC!HRB}@^RSwgtY3%y;L5H7??AQHKX z#F~&*!9vPB*@7IjD@h_3$8$c#cCIN77z7i1e#aDaS6Fp=1cMH3g*7e>3B=ETD8sgRcp)I zs=C9RBdPxw@Myz@`mhAYxqIzmwe28O`}=H&Jt+wWNrW2h~BygpCW_8MrNyZblSY-XqxL@*Sqp z7XZhrm{#D(MYzzJ32z>n4Ffj}@7}C;tS$grV&J6^g?ANb$>{>*EPx?I4f+fx6QY%! z$Rvcdu#AA%ZB|0mw3Z=SIo`P^l}wF$03D)u<`9)MPjhCq8(1m^A1r}da`(0BDrwRmNJh-ygCLcm`Acnlln1FaX8O~ z!hs290SB_of+X+bwkD31u+7z!YZkUJyX8tkVPeFoOkiR}6%%mSeDH$uu`fQ$?AbB!Go^#CQz1{e zucQ-Yi;%sfi5dn!6}(`=#DznLyU7AN85?AR`~`Q@IKkB8x^;ai9o*$WCGb?z0boJ< znq=ctQZTWhiWG>j0%uLc3~n*>0g5xIA(QHS7+J;)l6um-V(n^E`h1`djsDCa}M@?8wAtG`LzoP#ZjWt4U>IjYj_^NFDI(ZJGTyvazTkmsLN zheXjs@43C+%OiBQ&kFns3 z1LyNBzQ|&m#aCE-l?AgKhdUmJ%;oTfxWm(4=Q}K@>>fU)Vil*(4&MZDC7N>b&06Pr zr`_pvu5`9Ko1G1m^6B^Fc$&Py_X!TZRl;Y@zlUrOr*W_y{CXJ7_@5)jVsgNf-X}3T z@@6DIWy$t<=B0`;FUL2h#By#ad7C8q63OX`a;k4m_~osj2=E*fC2u@lGp|E`Ks?Hk T&{mDd@vqdOGW@Q$U)=m3o|%l1 literal 0 HcmV?d00001 diff --git a/test_project/tests/manager_utils_tests.py b/test_project/tests/manager_utils_tests.py new file mode 100644 index 0000000..e69de29 From fac0ee6ce5bbd805791c3b02640cbe1ba7fdda7b Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Tue, 4 Mar 2014 15:55:46 -0500 Subject: [PATCH 02/24] going to remove all the accidental pyc files --- .gitignore | 16 ++++ .travis.yml | 16 ++++ manager_utils/.manager_utils.py.swp | Bin 16384 -> 20480 bytes manager_utils/VERSION | 2 +- manager_utils/__init__.py | 2 +- manager_utils/__init__.pyc | Bin 210 -> 254 bytes manager_utils/fields.py | 56 ----------- manager_utils/fields.pyc | Bin 2346 -> 0 bytes manager_utils/import_string.py | 19 ---- manager_utils/import_string.pyc | Bin 886 -> 0 bytes manager_utils/manager_utils.py | 12 ++- requirements.txt | 1 + test_project/__init__.pyc | Bin 148 -> 147 bytes test_project/models.py | 3 + test_project/models.pyc | Bin 1616 -> 1049 bytes test_project/settings.py | 2 +- test_project/settings.pyc | Bin 2835 -> 2857 bytes test_project/tests/__init__.pyc | Bin 154 -> 153 bytes test_project/tests/manager_utils_tests.py | 112 ++++++++++++++++++++++ 19 files changed, 162 insertions(+), 79 deletions(-) create mode 100644 .gitignore create mode 100644 .travis.yml delete mode 100644 manager_utils/fields.py delete mode 100644 manager_utils/fields.pyc delete mode 100644 manager_utils/import_string.py delete mode 100644 manager_utils/import_string.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a470e9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Compiled python files +*.pyc + +# Vim files +*.swp +*.swo + +# Coverage files +.coverage + +# Setuptools distribution folder. +/dist/ + +# Python egg metadata, regenerated from source files by setuptools. +/*.egg-info +/*.egg diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8162140 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: python +python: + - '2.7' +env: + - DJANGO=1.4 DB=postgres + - DJANGO=1.5 DB=postgres + - DJANGO=1.6.1 DB=postgres +install: + - pip install -q Django==$DJANGO + - pip install -r requirements.txt +before_script: + - find . | grep .py$ | grep -v /migrations | xargs pep8 --max-line-length=120 + - find . | grep .py$ | grep -v /migrations | grep -v __init__.py | xargs pyflakes +script: + - coverage run --source='manager_utils' --branch manage.py test + - coverage report --fail-under=100 diff --git a/manager_utils/.manager_utils.py.swp b/manager_utils/.manager_utils.py.swp index e54ecb4ae0a759a9bf71f34950a0a71d426d0efe..a95b4022c9bb211de9bcd97f3f07b5ebe0178961 100644 GIT binary patch delta 1235 zcmbu;Ur19?9Ki82wfvL18K^L#4wF(zW)WqjLQ+N&5}_7pHLmWavpMH>MFnF)Xvm5h$Y zYQ1@6pW}{wKqf@CGtqt3tQN9*ePWtfJjDQ7;KzBCqZIS$LVUnO3?YbCoJBg~@I6h4 zY23mUbfNTnEmq~if@qYItrzya*V3>lol1STN~m&+9` zjV4{T_&R+7ceGk28~j>0ER813kPTWu*6Xr8tb3&vmYQtQ0$QUUinwKkAvJ$U*SuZQ z7iiK$K0}Ss<)LOR(AX*~V;!x@v;gD1-f(mUwQy_=t5c2P^M|eXJgY2Nogvn!_Lsk( zizcICJ}SsCU%67v#nOf9*r)$^21CApA;Y@=oV!ssJZcMqNXc?XyGUP4ur*}JF|9Ki9n>sDr~yMmgD!pk)<$yM~wKep%+c&Nocbm&sM?pvK)-F0Tx>e6o3 zp`aofFsUH1gAo$gLYNgAR1g((sI-%09zr@Q7GC7k6+K`%w#trF6-?owqBe;xS zByb!JD8g*65L3Bq*(5jp=z|Z%nAjpj3u^Fgvk(_h0SA`Yts&e;3N5HZ4zfGB;1c2& zZr~ygA%JQuvD=H7!8Aq#T!s*ZLK|FI<{Y149K#rZAIlDsV+ul8315kIz4A9a)_kzs za{6bjiK^O^49MI zQ7vXFeM1(1G7$-xs=B5oqN1T>%rvrzQ|n3St%pxGMWxxP8S-q%kRh4SRjoRw8K&0L zAy=2VN3Ld+nrXMRjmH?q0l2Vi=X*~( zh)d|lVKm?~W&8rqL1d!deQt-eUIk0xv+h+M%wPKoU71PU9+fN46x+Q*yIuI_pez0Y D2uIlf diff --git a/manager_utils/VERSION b/manager_utils/VERSION index d917d3e..49d5957 100644 --- a/manager_utils/VERSION +++ b/manager_utils/VERSION @@ -1 +1 @@ -0.1.2 +0.1 diff --git a/manager_utils/__init__.py b/manager_utils/__init__.py index 8a5d527..c4f3e4f 100644 --- a/manager_utils/__init__.py +++ b/manager_utils/__init__.py @@ -1 +1 @@ -from .fields import CallableField +from .manager_utils import ManagerUtilsMixin, ManagerUtilsManager diff --git a/manager_utils/__init__.pyc b/manager_utils/__init__.pyc index e2dd1b4ee831d414ca592ccb12beed9df22ab02f..511eaac7901de13c891f2cdde841ce12da7b536e 100644 GIT binary patch literal 254 zcmZSn%*)kcDi)l~00m4y+5w1*rGP{V5HT|3FfimYGDI;l&wr(`AOrRVEH4bTM}ppRmletdjpUS>&ryk0?N V2?x*wo80`A(wtN~kPXEk!vP6oI-LLj literal 210 zcmZSn%*z!~E)<;100m4y+5w1*MSw&K5HT|3FfimYGDI;lFs3ju1Zyw@B{CU-ga(Kx z;RO=Ti8(omNja%*nW;G`ei|S%O4xuz8i-#U1Y|L2pc2LQK)!xrZc=7RW`3S-Zf0?^ zeo9tiUV6T6GSo0#ux@=AHy*-`kI&4@EQycTE2u2t0GeTwo1apelWGTYOEJg}04ahj A@c;k- diff --git a/manager_utils/fields.py b/manager_utils/fields.py deleted file mode 100644 index e5a6427..0000000 --- a/manager_utils/fields.py +++ /dev/null @@ -1,56 +0,0 @@ -from django.db.models import SubfieldBase -from django.db.models.fields import CharField - -from callable_field.import_string import import_string - - -class CallableField(CharField): - """ - A field that loads a python path to a function or class. - """ - description = 'A loadable path to a function or class' - __metaclass__ = SubfieldBase - # A cache of loaded callables for quicker lookup - imported_module_cache = {} - - def get_prep_value(self, value): - value = self.to_python(value) - return self.value_to_string(value) - - def get_imported_module(self, path): - if path not in self.imported_module_cache: - self.imported_module_cache[path] = import_string(path) - return self.imported_module_cache[path] - - def to_python(self, value): - """ - Handles the following cases: - 1. If the value is already the proper type (a callable), return it. - 2. If the value is a string, import it and return the callable. - - Raises: A ValidationError if the string cannot be imported. - """ - if callable(value): - return value - else: - if value == '' and self.blank: - return '' - elif value is None and self.null: - return None - else: - return self.get_imported_module(value) - - def value_to_string(self, obj): - if type(obj) in (str, unicode): - return obj - elif obj is None: - return None - else: - return '{0}.{1}'.format(obj.__module__, obj.__name__) - - -try: - from south.modelsinspector import add_introspection_rules - add_introspection_rules([], ['^callable_field\.fields\.CallableField']) -except ImportError: - pass diff --git a/manager_utils/fields.pyc b/manager_utils/fields.pyc deleted file mode 100644 index 54f6cd2aeaa6de9d434edf2269f8c98008078eec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2346 zcmb_dO>-PI5N*xuH!)d4Vo2eG4nR!`d!2Bk0)=fzk&6$rReWrvP-$nh-m$eaTaq@E zEBHeAZ~P#B0Ccx@*Kwe@;8{zST2iau(^D(`b7%1Kk9*?+)?XX{e~rcehE<7w0xZBn zz(jzcegZauMGBKN_EWGeShQi%#(oP-3Nry^8-53P4e-;X1D7AOqYl_EEP62MfiOKd zLtkAu6Yx3#@k9L{v`il^2H>th2vH08vFyP(>th(zU-)GLTj35?>O8G77>oY~D+2@-kxmiI!fNkFyx4z=ZjN{vMgCt4iUg-_$_a(Aht;nD=U5}r0b}+$Rt= z__olW)^G6e@CNJ$xIPNSlxoqGD{EAti@C{(6~>&?aRv&3PU}|TzK>1`?pqPuN3*A0 z3G?nXYKnnTkFgOpr|O2pz!(b(_96lIXno4qKno8faM1&I%mXPkP|0KBr?t@n88V{P zckx27_DHAJMWqj-&+HP$x2;AN5p-aY@tcG8oPd_LnTd}Ojhgxh9`!e!`^CgD0SxupBe2xa&pmJSNkn%`= zuWeOo(&V?!;q|IwtkEeht?MSpsabnB<#jN~F~Dg?S`R&}HrufJr&iY&tc%m8Hr%LJ z*5>cv#B1T?UFu@FtviyF3LawdbXlNxN3@a$q9w2-|GXd1aLQIGcr!u*GZaGy+}>6- zz~b1atT#r%L`|HxY5crH33^N;4C&MpoL7&pMz5Y+Wz=TebhLrR6I>wcs;-KrG&$vw zKuVr9ZlOaMU{h`GnPN_rs`bJsbxj$2YNqG6${L;FXIMOi0OCVrjF`*lwDMi5C>3z< zN|jBaRG^{K_`+375>Jj@;CBQOn4mdJE0t5`%t;-5@_alw?RFhLxBh>2%!y%Q*HcpL zq#c33q?7bp{dWIeeE2@V5nYz5s)K9%((s$29HuqTPsz_43CGm?Via@li_yuOZ{ul3 zECt#oCh$=?9btAf)<+g-o3}^Su5x~<_i)0^twuNAV@|IOC=l<+~+8K%1jsThj;Vn_T9-X0)! diff --git a/manager_utils/import_string.py b/manager_utils/import_string.py deleted file mode 100644 index 63afca4..0000000 --- a/manager_utils/import_string.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.core.exceptions import ValidationError - - -def import_string(module_string): - """ - Imports a string as a python object. Returns None if the module could not be - imported. - """ - try: - path = '.'.join(module_string.split('.')[:-1]) - module_name = module_string.split('.')[-1] - file_name = module_string.split('.')[-2] - - module_path = __import__(path, globals(), locals(), [file_name]) - module = getattr(module_path, module_name) - except: - raise ValidationError('Could not import module {0}'.format(module_string)) - - return module diff --git a/manager_utils/import_string.pyc b/manager_utils/import_string.pyc deleted file mode 100644 index a3003a3b3c2530555f8c27391da68dc954da307a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 886 zcmb_b%Wl&^6uo0RZ3(JCt%MX&&@8=)!v`RQR9LX-B9#DjV@>QyGSS!*d2S>s>=E`kw*LmW_JMUn)Dmq(u=l`-ByOGf zH#+H}`=OX7T}+IqAdYE|qmzJbOe;_&)Fs3tY7=TB+DFul=tF%*LI^&fGNH|3;KNzC zz6h!XeQZZ)CusEOn8p(8UA1)$d#R-7#?&jRg1x=wwX3CDY;?hyoNL}SwU-yJ*3v8` zueGdPxwBdpZf8qbJC+OG6Plh-ms#&|m`v7B&-}y){Zshof1VkNZ@E6d=+YzP(I+P6 zOpW%Sb!!dt7*?JS^?A;Ra97qXl=bMA$hsn!eOR+%Zsv$mpIf@7Qk;e|J_ZcR^P%7V ztgYfTKfFuTs?z)b{?b^T_ugOGzB;fCL@6xl!M}kuQ`N$RFJoF6U(Cvls#k6bbyh8` zo-U2H8VA!r6rj;#S>HY0l}>cTmS$7 delta 38 tcmbQtIE9g&`7fgc5S_IhCruMtfrJDnAAKOnjRQgzP$?2ZMaTgpd|5g6#@*nL%DX|$4JrHx z{wF^GW@b%WE?mHhC-b%IdGC$?V>0A)2}^RApTJ0OO0S z$x79Iu(~q6Zz;cQJEhC!joQCg#$M2=KSqCrJ?1eQ1vIhpVC(lV4j|G1l2z*V&msOitcie9eXt~Gb#SrDE J;?#c>`~v#O=O_RG literal 1616 zcmb`HOK;Oa5XZ-MUQH>g5Eq1yEOGHAiQo!F(28=RhYc5Uu^jIvaah|?b|ab_QaJE^ z`2aBgb)KR~h{lP>JD&ACemlFt&$IS?@~EHE`U?2@mT&Weh2+0Snke^Z;E@O2C+$-n z&>&zvAm!1tMk9}8jlL7Dh&~VMBLKiX|mGjUlW3yr$>N1gdCWQ>utx6Y&7nY-NwzLx-jn*j` z=fSqwSDxI)S`}kE$-c1~)mhjUi#RTlT*a}Q6~`QGp`kXNO_pgK3s?OqzsQ&I#&^Uj z?Sy&7YA?x$8FK05nMr$cniS)*ldchUMqt!K5%FwZPE~4q70&K#DSZAOfK3g9py9cM zu5@C|{YbuNfB#J~MSN9p6j+!YJ%L08I11!lK>|^Rsw`wp1 zb5r&ZIVN`@!aI1f>xSC2$FXst>mT#I!v%OZ3$2eu>jnF}i&nug{tgTEW5z$Q5;%Ya z(mpmEbk0-0w{UKv6ILxmwQaj{C|Dtjshc&5-4?D|9gFUI&TUJ8_0C+N#a4nixCqx<^&g7vr9nk`Oz%~Zz8o7PRfG~*ZPI8yfj delta 347 zcmX|)%TB^j6h(I}1}$iORzZ!5uL2hERnf#ha0IjKfE0Em*Ay5);$xx%e}MEWOq}rt z{*rU!DMXXI&tvU#&i$|>J86E;S*7QtVX|VJ4Xcl@=TOVw1=I?-2(=1&P&Rl8wFYLO z*1;@P2CPD5!7Hc@=xHzwZo>-_;0`=(KpuC&96auc_9xH;55U7QY|DgT9)XUeyr^*d zKbA-A7X(fg0)R#7jzum!wHg1Qr@#_e7FGdIB+ZJo1)ierT#sH1+sw@aX!&92H^ZQD zuY-_-lpsF%-FC0h?e&AvSK{4VNC>Q1aft0&dUQAQV>mZbm;Fvt4|K2Vc67g0o@}~* UH{J2g=*=n`Ptm7UkBpS_3n}GHC;$Ke diff --git a/test_project/tests/__init__.pyc b/test_project/tests/__init__.pyc index 34af86d25df46fbe01e00aa2d8612fcb5f857a96..c9a0a7aa802bb3b69463d105763d7a97d7769c64 100644 GIT binary patch delta 37 scmbQmIFpf``78VA!r6rj;#S>Fa0mI=7bN~PV delta 38 tcmbQqIE#^;`7 Date: Tue, 4 Mar 2014 15:56:51 -0500 Subject: [PATCH 03/24] removed pyc files --- manager_utils/.manager_utils.py.swp | Bin 20480 -> 0 bytes manager_utils/__init__.pyc | Bin 254 -> 0 bytes test_project/__init__.pyc | Bin 147 -> 0 bytes test_project/settings.pyc | Bin 2857 -> 0 bytes test_project/tests/__init__.pyc | Bin 153 -> 0 bytes test_project/tests/callable_field_tests.pyc | Bin 9510 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 manager_utils/.manager_utils.py.swp delete mode 100644 manager_utils/__init__.pyc delete mode 100644 test_project/__init__.pyc delete mode 100644 test_project/settings.pyc delete mode 100644 test_project/tests/__init__.pyc delete mode 100644 test_project/tests/callable_field_tests.pyc diff --git a/manager_utils/.manager_utils.py.swp b/manager_utils/.manager_utils.py.swp deleted file mode 100644 index a95b4022c9bb211de9bcd97f3f07b5ebe0178961..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeHNU8p2S6)ux#G@3*d;$zBP@Xk$UXE#4&Wy641cViYwqHZ=~{+gQUnwj3)(>>{` zzV{A9M1&;Z!-B*oArFEt5fwoUC`j^>zqh=&g1$%sA%Q$3m>=?zeCJeEci&-h+hdhxnJhXSNr?@EA{xvfUnT++)CLO<1I1}8Hfx-1|kEIfyh8)ATkgchzvvq zA_I|u{{jOl72N(SI5EEAVq*4&=Z#a0(a#uU#d?zkojhPXY^| z1Reyg0xkjGzEX%6fnNbX1NMMjKmligH@+amQ^0S4$ACwHM*s~x0KETsAzlJr0DcSn z2zVIy7H}MR>k1*>1YQPy3ET=C1+D>p_c_P{9tM60+yG7uY&7;0L1wtz(c@&z+J$rn2SFEzXq1T9xQy0_W14Te%zSXIcf(6X##QOX^a*Sat9Oz(~Lf=-#p%*aaNU`p!v zpk7!5-?U~Wm;-e zY*@?`hHPa8J&7~>AHs4BI0s`|`T$Sfj*gw1!BG*q38CvejwJG8v7Ufl)iUS1(f3 zCX-C(sTsPGY27Oj1n*PURy~%u-IqK2n@nI7JthC}G6tEYz|nKlgJwYvto8NeibZLe z#tv6qmF3V)C;Q_7HJBU25dj{YSv@eL>fD-P_(HU7@2YCru_DkIw4gi1vlH3UP0vH` z6Y}nvHrhpzk&CL_$x_YTARG}>z%@8`?9Ox{$1^?NMsP}^f@Jed>SByhCV~WaFAmK{ zXd1#Q*dSSCRv2AblKD}(dMZ~`RTYvNPiyJ~wcUwyUo{~6mdJCal;w`DDzZ)$OVQPn zz9U6q_GMZlzNXHbkV*EFM(tSV&QKwPG7*M`!{F>^(#*;_PhExy8JN3!UO4p>8hP24 zf@Fs)5VXc((s5M<4CqMIXbBJw2|i zi;Gd6Z+jE=XB(IAnqJ^6*TsGd;+ZrqlYB>EhpaZ;`S4-hk)ZAqJTg93lgz4&6LL%D$nMAo zUOv2=Hy|!n2l6gjap6tKI<7O8o;>d~X~v>48)HmYGC%c+&9CV*VO2t2^G5p(&r_c!nGt=4N?va=5OC9=DVVn;lKZBXl_ zZdz!hefuP8;1`{s4M?Xrt@O0cRkd~}=TaKB0<y<6yXer(CT)$VCyImkM8@7vn zc>rr^%IA4iE+=&{rcIa1GrOb|V#@_nO**aFB+{_7z+#o7h;L@aQ^8il%wCoz$<*rM zvDPVrl@iy*vI_(g4FzNuh5nGY3(aGG9{RHQctpicmH@%(sov+v}SUwcn6n!?!1i$-Z5a+R*OZR@e(b08;S+k zxDZ_+4DL(u#G1-#3@3()&NUMcya@;me_UXg*wKx7~?5E+OJLCMMHcvTnBV`O!vFAB~lez%2k({u}L{2c83- z1!{A giQ7}e&B5Y>&X4?m9w**+K4>!>BIU!ht4BdT1kPP&wr(`AOrRVEH4bTM}ppRmletdjpUS>&ryk0?N V2?x*wo80`A(wtN~kPXEk!vP6oI-LLj diff --git a/test_project/__init__.pyc b/test_project/__init__.pyc deleted file mode 100644 index 0d7e60c9793b9699133b8e5ea7a7a69c7eb67890..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147 zcmZSn%*)lRE*6~300oRd+5w1*S%5?e14FO|NW@PANHCxg#Wp}O{lwg)%#zIfJl)*P z;$;1lti-(ZeBIo{yu|d>BHhxG%$#EVlGNgo_=2MRtkmQZ{rLFIyv&mLc)fzk5)PmN SHo5sJr8%i~AZv<&m;nH+T_DE* diff --git a/test_project/settings.pyc b/test_project/settings.pyc deleted file mode 100644 index ebaa402b68fa423b49087ec1399d8c2b9e7bace8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2857 zcmd@W+j84P^f*qOdwPS?77Fwda1*SwO`EpRVX9h+qp2)sB$*C+KqGtCj#622y{m-e z=lCdofU`%gqhSie3&R7}?B(1qd(N(_zptF(%QEZ*OTbnjSO&HV!3wZ7 z2v*6;8qMpZe*@S$1ea*OO!F0B8xULt_7s9^!1f^c1lSb_J_Ys+g6qJZL+}}}n-JUp zb_;@=z+OOb3)uG%d`|AI0s9*E(0g_V_LR%$?_FT`(5?G8HVY(r{|&Hj2{}X#M6)`u z2e@qE*q(81fg&S3<;O&^?+|L>&@jv|C@q+@9bgX;_XyZyTvP^b1KS0!{)5W#S2(fb zC>>^DnhCBsIQd*iFOMa6^EBlXacjn-v7Xk8`tNwkgeKl6$g?YJNH(38<(RbPsf^{B zoJXvqw@un~J=2v-h&j|f?`_95xX=c1$}kFTA0h8dNIu#xFwg#( zoIV~K@r3tfHi@~I%05Qf`pJv!JEvRu$^FgkhuQh@yYc3W&G47WJuY`yzFj}5%dOM; z@zbY+-uY4dif=s=4=zliALAsFTuZWu{3CD4Kh2(^{)Rgmk3)9h)(4T2fl8F^VmbgEX=qQ&*WfMBW zs&e{xq*+>${fLVrbSE0($(CGf7r2t(6^u<{C5Pilc8sT|<++keKMieao_GLxxk zxjM@Ub%b0;cod~*a1!P+PJ~=T%=?^A&U$&A;L%A{p(4Wwh*UO81$~0mhe633N^>lt zUcy8EYb<1(4#OlHqGloPDk3$aMpm#ahMLRY=Xk~j@8!e&C=p!DAzS@{yoSr9Acjm) z!+|1`Ijkdz8p$iubvrKY5`Myyk6Y2%C{E2$6ekB!iX~G7)K4NIC>x3rMv6&L6CV{^ zf|?k!JC24}$716m0_7T}p4|BmVB><^&s0rD{Ud&Ho8Vcl4I&dekX~?(460&nzI{ zYMWuu!ADcMY3oj_i>zU@W0+LY7=%GWl6vYpu3cPI^mL9sD zj!)JKuZ}6Xw`|i34GaBM_e&YMZ#jmFgJjL7Yx?0E^JkKs z<;?Er#B)_SV;P2RzSUif$i_DxgQg=v??N=%>^S=*{MKt(p?-L1TSXUBGG-dd6?HC{ zPhYof(@?w~<;U){@GnP;ve49<2c}oL08j+POPqG1Q7=|6N@Em@{!VehHm28kgD%>g za&_$B9jdNnp^eWd+b>5M%M<>i`ZFTVajI2n)!H0>^R;TVQmrgbzl~MIC@kV}EYxbW lTZRhm=hY0A;zL+zSe(I2g%+jl(p2Zlbhm=quPzjJ{s5^Z6b=9Y diff --git a/test_project/tests/__init__.pyc b/test_project/tests/__init__.pyc deleted file mode 100644 index c9a0a7aa802bb3b69463d105763d7a97d7769c64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153 zcmZSn%*)lRE*6~300oRd+5w1*S%5?e14FO|NW@PANHCxg#ZEvm{lwg)%#zIfJl)*P z;$;1lti-(ZeBIo{yu|d>BHhxG%$#EVlGNgo_=2MRtkmQZFsE2QK0Y%qvm`!Vub{Go V189s*ZhlH>PO2TqvSJ`+005bUBQF2| diff --git a/test_project/tests/callable_field_tests.pyc b/test_project/tests/callable_field_tests.pyc deleted file mode 100644 index ccdee77b725d24842aef2881c61b60729e141afd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9510 zcmd5?TXP&o74F%qb}dVCj6)2DOa%-J#1i*wph8rnfC@`xY)G;%Q=^@cG^^PQ)3Y|B z?3dWZrHU#SZ@lrs3&jJ!fM=fao+^F@KLEb(^xRgSl`P6@Mbh^6^z?N1Ip;gyIo+fB ze=c|b`mYym4^{e8$M@^_=)a-x@n@+>sj;O7ma=$VQ&CNg>uOLp*L4*&)VQezO>^B) zQA>^6YS32WH8ogM+9Ea#{In%I~O0m}x`#T@`Gq zeM|Y5c=w@F?+vz;zpjF&v|i3zuPA>*-R^B-wc&s9)3Z=ZnkZ&oGCVYwy!%Tp3VkmL zr;{JW@iaC!)^Ouapp#us2fYTLyo}<_`DDnBKb!hNlyuRu8+lrr%MDz<5qXoNJMla) zO?;+5kD|OK*CrRUi;tGE6&A`C{T7O@jh`Rd`{`VEa_A*?sBKIcB!O>-6MH%d>;$W` zy@`*D=-8fX+}S%eoq20phB3l$4lbdGZ&eif=eXSQ#(N=Ux-|~)}cdmp%0M>^PwoWTkxEr4Svo=o$*uI7|H3(5X`V zI7sI4MB9|Y-k-)IeK$EXSA*Iw-A}Xzmg}Dh9C6aETA(NEm%s#oH z@8^d8E^ePHX_#mml8I@&$oArc`8b$lX2sLW*tU)uZWi*cMAUW6LQyf3NcwSHr;>)K zs$nK7zbnN37Mjl#_b52NH;w(}veJ2v?yX*0Vwlp(Tq5tM(UQt5MDW(s^9gWq{~Xl> z`5Wx{DvAJX2AV=yBUgeR$zXNm*M-&^%5O-eshrzAN~XVoB306mv|^n4-eEAw;U1;) z+R`C|y)glizzG#;vA&WV*Z;uZrxPpsaosO)a;`vG9qUO<(Xk=TDD$GexPYwG)JW$m_5`lsz<9zfR1GMeeyyTt zI*Icf)=K8(S_Q{nE>_!Xp`IAUPrPy9x)RG=cRcmyU~M;{l&EFWc1qq6zAOj>Q>(a78Pi9cizd#|L z%q41DC)1e0iqV>Jll3gTBa+hB18qXL7YEYa#Cb+l8%7DDG{A{Z&_QTH`m6psibSbD zDD^uf8IYxZtJDKaCBiw76%_E$vL3&T91F5H(=6>lO<8bu(85FHZ|@-Mdiz~uN28V+ zK@Ip@Lvm$3z~gYZHFdwPMoSc=70+ik6y>6CPI|84kFbI1g^U`52GZU zxb8OSRu%+D=aV}012@>;hm@sEna_&}bYG}qeC!HRB}@^RSwgtY3%y;L5H7??AQHKX z#F~&*!9vPB*@7IjD@h_3$8$c#cCIN77z7i1e#aDaS6Fp=1cMH3g*7e>3B=ETD8sgRcp)I zs=C9RBdPxw@Myz@`mhAYxqIzmwe28O`}=H&Jt+wWNrW2h~BygpCW_8MrNyZblSY-XqxL@*Sqp z7XZhrm{#D(MYzzJ32z>n4Ffj}@7}C;tS$grV&J6^g?ANb$>{>*EPx?I4f+fx6QY%! z$Rvcdu#AA%ZB|0mw3Z=SIo`P^l}wF$03D)u<`9)MPjhCq8(1m^A1r}da`(0BDrwRmNJh-ygCLcm`Acnlln1FaX8O~ z!hs290SB_of+X+bwkD31u+7z!YZkUJyX8tkVPeFoOkiR}6%%mSeDH$uu`fQ$?AbB!Go^#CQz1{e zucQ-Yi;%sfi5dn!6}(`=#DznLyU7AN85?AR`~`Q@IKkB8x^;ai9o*$WCGb?z0boJ< znq=ctQZTWhiWG>j0%uLc3~n*>0g5xIA(QHS7+J;)l6um-V(n^E`h1`djsDCa}M@?8wAtG`LzoP#ZjWt4U>IjYj_^NFDI(ZJGTyvazTkmsLN zheXjs@43C+%OiBQ&kFns3 z1LyNBzQ|&m#aCE-l?AgKhdUmJ%;oTfxWm(4=Q}K@>>fU)Vil*(4&MZDC7N>b&06Pr zr`_pvu5`9Ko1G1m^6B^Fc$&Py_X!TZRl;Y@zlUrOr*W_y{CXJ7_@5)jVsgNf-X}3T z@@6DIWy$t<=B0`;FUL2h#By#ad7C8q63OX`a;k4m_~osj2=E*fC2u@lGp|E`Ks?Hk T&{mDd@vqdOGW@Q$U)=m3o|%l1 From 47ab77aee1fc1213c51d503aed4f9e89325f1dc8 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Tue, 4 Mar 2014 15:58:20 -0500 Subject: [PATCH 04/24] removed more pyc files --- test_project/models.pyc | Bin 1049 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test_project/models.pyc diff --git a/test_project/models.pyc b/test_project/models.pyc deleted file mode 100644 index 3c84346d8bfa27114810e939d6415fb9d7795ae1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1049 zcmb_aO>fgc5S_IhCruMtfrJDnAAKOnjRQgzP$?2ZMaTgpd|5g6#@*nL%DX|$4JrHx z{wF^GW@b%WE?mHhC-b%IdGC$?V>0A)2}^RApTJ0OO0S z$x79Iu(~q6Zz;cQJEhC!joQCg#$M2=KSqCrJ?1eQ1vIhpVC(lV4j|G1l2z*V&msOitcie9eXt~Gb#SrDE J;?#c>`~v#O=O_RG From 6b3bde0269ab85c85a00bb479c90dace52e9317d Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Tue, 4 Mar 2014 16:21:32 -0500 Subject: [PATCH 05/24] tested bulk updating --- manager_utils/manager_utils.py | 6 +- requirements.txt | 1 - test_project/settings.py | 23 ++++-- test_project/tests/manager_utils_tests.py | 91 +++++++++++++++++++++++ 4 files changed, 111 insertions(+), 10 deletions(-) diff --git a/manager_utils/manager_utils.py b/manager_utils/manager_utils.py index dc26d33..9c899eb 100644 --- a/manager_utils/manager_utils.py +++ b/manager_utils/manager_utils.py @@ -47,16 +47,16 @@ def bulk_update(self, model_objs, fields_to_update): fields_to_update: A list of fields to be updated. Only these fields will be updated """ updated_rows = [ - chain((model_obj.id,), (getattr(model_obj, field_name) for field_name in fields_to_update)) + [model_obj.id] + [getattr(model_obj, field_name) for field_name in fields_to_update] for model_obj in model_objs ] - if len(updated_rows) == 0: + if len(updated_rows) == 0 or len(fields_to_update) == 0: return # Execute the bulk update Query().from_table( table=self.model, - fields=chain(('id',), fields_to_update), + fields=chain(['id'] + fields_to_update), ).update(updated_rows) def upsert(self, defaults=None, updates=None, **kwargs): diff --git a/requirements.txt b/requirements.txt index 6b0cf2b..f32d5bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ django-nose==1.1 pep8 psycopg2==2.4.5 pyflakes -south==0.7.6 git+https://github.com/wesokes/django-query-builder.git@0.5.2 # Note that Django is a requirement, but it is installed in the .travis.yml file in order to test against different versions diff --git a/test_project/settings.py b/test_project/settings.py index 4e432af..45fe16e 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -14,13 +14,17 @@ MANAGERS = ADMINS +# Setup celery +import djcelery +djcelery.setup_loader() + # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # postgresql_psycopg2', - 'NAME': 'entity', - 'USER': 'entity', - 'PASSWORD': 'entity', + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'ambition_dev', + 'USER': 'ambition_dev', + 'PASSWORD': 'ambition_dev', 'HOST': 'localhost' } } @@ -117,9 +121,16 @@ ) INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + # 'django.contrib.messages', + # 'django.contrib.staticfiles', + 'django.contrib.admin', + 'entity', 'django_nose', - 'manager_utils', - 'querybuilder', + 'djcelery', 'south', 'test_project', ) diff --git a/test_project/tests/manager_utils_tests.py b/test_project/tests/manager_utils_tests.py index 1a58909..163ac86 100644 --- a/test_project/tests/manager_utils_tests.py +++ b/test_project/tests/manager_utils_tests.py @@ -110,3 +110,94 @@ def test_mutliple_to_single_using_queryset(self): model_obj = G(TestModel) G(TestModel) self.assertEquals(model_obj, TestModel.objects.filter(id=model_obj.id).single()) + + +class TestBulkUpdate(TestCase): + """ + Tests the bulk_update function. + """ + def test_none(self): + """ + Tests when no values are provided to bulk update. + """ + TestModel.objects.bulk_update([], []) + + def test_objs_no_fields_to_update(self): + """ + Tests when objects are given to bulk update with no fields to update. Nothing should change in + the objects. + """ + test_obj_1 = G(TestModel, int_field=1) + test_obj_2 = G(TestModel, int_field=2) + # Change the int fields on the models + test_obj_1.int_field = 3 + test_obj_2.int_field = 4 + # Do a bulk update with no update fields + TestModel.objects.bulk_update([test_obj_1, test_obj_2], []) + # The test objects int fields should be untouched + test_obj_1 = TestModel.objects.get(id=test_obj_1.id) + test_obj_2 = TestModel.objects.get(id=test_obj_2.id) + self.assertEquals(test_obj_1.int_field, 1) + self.assertEquals(test_obj_2.int_field, 2) + + def test_objs_one_field_to_update(self): + """ + Tests when objects are given to bulk update with one field to update. + """ + test_obj_1 = G(TestModel, int_field=1) + test_obj_2 = G(TestModel, int_field=2) + # Change the int fields on the models + test_obj_1.int_field = 3 + test_obj_2.int_field = 4 + # Do a bulk update with the int fields + TestModel.objects.bulk_update([test_obj_1, test_obj_2], ['int_field']) + # The test objects int fields should be untouched + test_obj_1 = TestModel.objects.get(id=test_obj_1.id) + test_obj_2 = TestModel.objects.get(id=test_obj_2.id) + self.assertEquals(test_obj_1.int_field, 3) + self.assertEquals(test_obj_2.int_field, 4) + + def test_objs_one_field_to_update_ignore_other_field(self): + """ + Tests when objects are given to bulk update with one field to update. This test changes another field + not included in the update and verifies it is not updated. + """ + test_obj_1 = G(TestModel, int_field=1, float_field=1.0) + test_obj_2 = G(TestModel, int_field=2, float_field=2.0) + # Change the int and float fields on the models + test_obj_1.int_field = 3 + test_obj_2.int_field = 4 + test_obj_1.float_field = 3.0 + test_obj_2.float_field = 4.0 + # Do a bulk update with the int fields + TestModel.objects.bulk_update([test_obj_1, test_obj_2], ['int_field']) + # The test objects int fields should be untouched + test_obj_1 = TestModel.objects.get(id=test_obj_1.id) + test_obj_2 = TestModel.objects.get(id=test_obj_2.id) + self.assertEquals(test_obj_1.int_field, 3) + self.assertEquals(test_obj_2.int_field, 4) + # The float fields should not be updated + self.assertEquals(test_obj_1.float_field, 1.0) + self.assertEquals(test_obj_2.float_field, 2.0) + + def test_objs_two_fields_to_update(self): + """ + Tests when objects are given to bulk update with two fields to update. + """ + test_obj_1 = G(TestModel, int_field=1, float_field=1.0) + test_obj_2 = G(TestModel, int_field=2, float_field=2.0) + # Change the int and float fields on the models + test_obj_1.int_field = 3 + test_obj_2.int_field = 4 + test_obj_1.float_field = 3.0 + test_obj_2.float_field = 4.0 + # Do a bulk update with the int fields + TestModel.objects.bulk_update([test_obj_1, test_obj_2], ['int_field', 'float_field']) + # The test objects int fields should be untouched + test_obj_1 = TestModel.objects.get(id=test_obj_1.id) + test_obj_2 = TestModel.objects.get(id=test_obj_2.id) + self.assertEquals(test_obj_1.int_field, 3) + self.assertEquals(test_obj_2.int_field, 4) + # The float fields should be updated + self.assertEquals(test_obj_1.float_field, 3.0) + self.assertEquals(test_obj_2.float_field, 4.0) From 546cef7b3d976476eff8695ef8eeba6196901d35 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Tue, 4 Mar 2014 17:09:48 -0500 Subject: [PATCH 06/24] fully tested manager utils --- manager_utils/manager_utils.py | 6 +- test_project/models.py | 9 +- test_project/tests/manager_utils_tests.py | 117 +++++++++++++++++++++- 3 files changed, 122 insertions(+), 10 deletions(-) diff --git a/manager_utils/manager_utils.py b/manager_utils/manager_utils.py index 9c899eb..56444ed 100644 --- a/manager_utils/manager_utils.py +++ b/manager_utils/manager_utils.py @@ -70,8 +70,11 @@ def upsert(self, defaults=None, updates=None, **kwargs): values provided in the defaults when inserting the object. **kwargs: These values provide the arguments used when checking for the existence of the object. + + Returns: + A tuple of the upserted object and a Boolean that is True if it was created (False otherwise) """ - obj, created = self.model.objects.get_or_create(defaults=defaults, **kwargs) + obj, created = self.model.objects.get_or_create(defaults=defaults or {}, **kwargs) if updates is not None: for k, v in updates.iteritems(): @@ -87,7 +90,6 @@ def get_or_none(self, **query_params): Returns: A model object if one exists with the query params, None otherwise. """ - print self.get_queryset().__class__ return self.get_queryset().get_or_none(**query_params) def single(self): diff --git a/test_project/models.py b/test_project/models.py index 4a8b268..a4acdda 100644 --- a/test_project/models.py +++ b/test_project/models.py @@ -2,17 +2,12 @@ from manager_utils import ManagerUtilsManager -class ForeignKeyTestModel(models.Model): - value = models.CharField(max_length=128) - - class TestModel(models.Model): """ A model for testing manager utils. """ int_field = models.IntegerField() - char_field = models.CharField(max_length=128) - float_field = models.FloatField() - foreign_key = models.ForeignKey(ForeignKeyTestModel) + char_field = models.CharField(max_length=128, null=True) + float_field = models.FloatField(null=True) objects = ManagerUtilsManager() diff --git a/test_project/tests/manager_utils_tests.py b/test_project/tests/manager_utils_tests.py index 163ac86..ab67e77 100644 --- a/test_project/tests/manager_utils_tests.py +++ b/test_project/tests/manager_utils_tests.py @@ -1,7 +1,7 @@ from django.test import TestCase from django_dynamic_fixture import G -from test_project.models import TestModel, ForeignKeyTestModel +from test_project.models import TestModel class GetOrNoneTests(TestCase): @@ -201,3 +201,118 @@ def test_objs_two_fields_to_update(self): # The float fields should be updated self.assertEquals(test_obj_1.float_field, 3.0) self.assertEquals(test_obj_2.float_field, 4.0) + + +class TestUpsert(TestCase): + """ + Tests the upsert method in the manager utils. + """ + def test_upsert_creation_no_defaults(self): + """ + Tests an upsert that results in a created object. Don't use defaults + """ + model_obj, created = TestModel.objects.upsert(int_field=1) + self.assertTrue(created) + self.assertEquals(model_obj.int_field, 1) + self.assertIsNone(model_obj.float_field) + self.assertIsNone(model_obj.char_field) + + def test_upsert_creation_defaults(self): + """ + Tests an upsert that results in a created object. Defaults are used. + """ + model_obj, created = TestModel.objects.upsert(int_field=1, defaults={'float_field': 1.0}) + self.assertTrue(created) + self.assertEquals(model_obj.int_field, 1) + self.assertEquals(model_obj.float_field, 1.0) + self.assertIsNone(model_obj.char_field) + + def test_upsert_creation_updates(self): + """ + Tests an upsert that results in a created object. Updates are used. + """ + model_obj, created = TestModel.objects.upsert(int_field=1, updates={'float_field': 1.0}) + self.assertTrue(created) + self.assertEquals(model_obj.int_field, 1) + self.assertEquals(model_obj.float_field, 1.0) + self.assertIsNone(model_obj.char_field) + + def test_upsert_creation_defaults_updates(self): + """ + Tests an upsert that results in a created object. Defaults are used and so are updates. + """ + model_obj, created = TestModel.objects.upsert( + int_field=1, defaults={'float_field': 1.0}, updates={'char_field': 'Hello'}) + self.assertTrue(created) + self.assertEquals(model_obj.int_field, 1) + self.assertEquals(model_obj.float_field, 1.0) + self.assertEquals(model_obj.char_field, 'Hello') + + def test_upsert_creation_defaults_updates_override(self): + """ + Tests an upsert that results in a created object. Defaults are used and so are updates. Updates + override the defaults. + """ + model_obj, created = TestModel.objects.upsert( + int_field=1, defaults={'float_field': 1.0}, updates={'char_field': 'Hello', 'float_field': 2.0}) + self.assertTrue(created) + self.assertEquals(model_obj.int_field, 1) + self.assertEquals(model_obj.float_field, 2.0) + self.assertEquals(model_obj.char_field, 'Hello') + + def test_upsert_no_creation_no_defaults(self): + """ + Tests an upsert that already exists. Don't use defaults + """ + G(TestModel, int_field=1, float_field=None, char_field=None) + model_obj, created = TestModel.objects.upsert(int_field=1) + self.assertFalse(created) + self.assertEquals(model_obj.int_field, 1) + self.assertIsNone(model_obj.float_field) + self.assertIsNone(model_obj.char_field) + + def test_upsert_no_creation_defaults(self): + """ + Tests an upsert that already exists. Defaults are used but don't matter since the object already existed. + """ + G(TestModel, int_field=1, float_field=None, char_field=None) + model_obj, created = TestModel.objects.upsert(int_field=1, defaults={'float_field': 1.0}) + self.assertFalse(created) + self.assertEquals(model_obj.int_field, 1) + self.assertIsNone(model_obj.float_field) + self.assertIsNone(model_obj.char_field) + + def test_upsert_no_creation_updates(self): + """ + Tests an upsert that already exists. Updates are used. + """ + G(TestModel, int_field=1, float_field=2.0, char_field=None) + model_obj, created = TestModel.objects.upsert(int_field=1, updates={'float_field': 1.0}) + self.assertFalse(created) + self.assertEquals(model_obj.int_field, 1) + self.assertEquals(model_obj.float_field, 1.0) + self.assertIsNone(model_obj.char_field) + + def test_upsert_no_creation_defaults_updates(self): + """ + Tests an upsert that already exists. Defaults are used and so are updates. + """ + G(TestModel, int_field=1, float_field=2.0, char_field='Hi') + model_obj, created = TestModel.objects.upsert( + int_field=1, defaults={'float_field': 1.0}, updates={'char_field': 'Hello'}) + self.assertFalse(created) + self.assertEquals(model_obj.int_field, 1) + self.assertEquals(model_obj.float_field, 2.0) + self.assertEquals(model_obj.char_field, 'Hello') + + def test_upsert_no_creation_defaults_updates_override(self): + """ + Tests an upsert that already exists. Defaults are used and so are updates. Updates override the defaults. + """ + G(TestModel, int_field=1, float_field=3.0, char_field='Hi') + model_obj, created = TestModel.objects.upsert( + int_field=1, defaults={'float_field': 1.0}, updates={'char_field': 'Hello', 'float_field': 2.0}) + self.assertFalse(created) + self.assertEquals(model_obj.int_field, 1) + self.assertEquals(model_obj.float_field, 2.0) + self.assertEquals(model_obj.char_field, 'Hello') From 15d93f102cc20625bfd249ecfd1b2782d698ae48 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:20:08 -0500 Subject: [PATCH 07/24] updated docs --- README.md | 138 +++++++++++++++++++++++---------- manager_utils/manager_utils.py | 85 ++++++++++++++++++-- 2 files changed, 176 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index b0b1a4d..6160ef5 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,120 @@ -[![Build Status](https://travis-ci.org/ambitioninc/django-callable-field.png)](https://travis-ci.org/ambitioninc/django-callable-field) -django-callable-field +[![Build Status](https://travis-ci.org/ambitioninc/django-manager-utils.png)](https://travis-ci.org/ambitioninc/django-manager-utils) +django-manager-utils ===================== -Store callable functions/classes in Django models. +Additional utilities for Django model managers. ## A Brief Overview -The Django callable field app provides a custom field for a Django model that stores a callable function or class. This provides the ability to easily store paths to code in a model field and quickly execute it without having to load it manually. +Django manager utils allows a user to perform various functions not natively supported by Django's model managers. A function overview is below with links to more in-depth documentation and examples for each function. +- [single](#single): Grabs a single element from table and verifies it is the only element. +- [get_or_none](#get_or_none): Performs a get on a queryset and returns None if the object does not exist. +- [upsert](#upsert): Performs an upsert (update or insert) to a model. +- [bulk_update](#bulk_update): Bulk updates a list of models and the fields that have been updated. -## Storing and Calling a Function -Assume that you have defined the following function that returns the argument that you passed to it: - def ret_arg(arg): - return arg +## single() +Assumes that the model only has one element in the table or queryset and returns that value. If the table has more than one or no value, an iexception is raised. -In order to save this callable function to a model, simply do the following: +**Returns**: The only model object in the queryset. - from django.db import models - from callable_field import CallableField +**Raises**: DoesNotExist error when the object does not exist or a MultipleObjectsReturned error when there is more than one object. - class CallableModel(models.Model): - my_function = CallableField(max_length=128) +**Examples**: - model_obj = CallableModel.objects.create(my_function='full_path_to_function.ret_arg') - # Call the function from the my_function field and print the results - print model_obj.my_function('Hello World') - Hello World + TestModel.objects.create(int_field=1) + model_obj = TestModel.objects.single() + print model_obj.int_field + 1 -You can similarly pass a function variable (instead of a string) to the model constructor: +## get_or_none(\*\*query_params) +Get an object or return None if it doesn't exist. - model_obj = CallableModel.objects.create(my_function=ret_arg) - print model_obj.my_function('Hello World') - Hello World +**Args**: +- \*\*query_params: The query parameters used in the lookup. +**Returns**: A model object if one exists with the query params, None otherwise. -## Storing and Calling a Class -Similar to the function example, assume that you have defined the following class that returns the argument passes to its constructor: +**Examples**: - class RetArg(object): - def __init__(arg): - self.arg = arg + model_obj = TestModel.objects.get_or_none(int_field=1) + print model_obj + None - def ret_arg(): - return self.arg + TestModel.objects.create(int_field=1) + model_obj = TestModel.objects.get_or_none(int_field=1) + print model_obj.int_field + 1 -Similar to the function example, do the following to save the class: +## upsert(defaults=None, updates=None, \*\*kwargs) +Performs an update on an object or an insert if the object does not exist. - from django.db import models - from callable_field import CallableField +**Args**: +- defaults: These values are set when the object is inserted, but are irrelevant when the object already exists. This field should only be used when values only need to be set during creation. +- updates: These values are updated when the object is updated. They also override any values provided in the defaults when inserting the object. +- \*\*kwargs: These values provide the arguments used when checking for the existence of the object. They are used in a similar manner to Django's get_or_create function. - class CallableModel(models.Model): - my_class = CallableField(max_length=128) +**Returns**: A tuple of the upserted object and a Boolean that is True if it was created (False otherwise) - model_obj = CallableModel.objects.create(my_class='full_path_to_class.RetArg') - # Instantiate the class from the my_class field and print the results - print model_obj.my_class('Hello World').ret_arg() - Hello World +**Examples**: -You can similarly pass a class variable (instead of a string) to the model constructor: + # Upsert a test model with an int value of 1. Use default values that will be given to it when created + model_obj, created = TestModel.objects.upsert(int_field=1, defaults={'float_field': 2.0}) + print created + True + print model_obj.int_field, model_obj.float_field + 1, 2.0 - model_obj = CallableModel.objects.create(my_class=RetArg) - print model_obj.my_class('Hello World').ret_arg() - Hello World + # Do an upsert on that same model with different default fields. Since it already exists, the defaults + # are not used + model_obj, created = TestModel.objects.upsert(int_field=1, defaults={'float_field': 3.0}) + print created + False + print model_obj.int_field, model_obj.float_field + 1, 2.0 + + # In order to update the float field in an existing object, use the updates dictionary + model_obj, created = TestModel.objects.upsert(int_field=1, updates={'float_field': 3.0}) + print created + False + print model_obj.int_field, model_obj.float_field + 1, 3.0 + + # You can use updates on a newly created object that will also be used as initial values. + model_obj, created = TestModel.objects.upsert(int_field=2, updates={'float_field': 4.0}) + print created + True + print model_obj.int_field, model_obj.float_field + 2, 4.0 + +## bulk_update(model_objs, fields_to_update) +Performs an bulk update on an list of objects. Any fields listed in the fields_to_update array will be updated in the database. + +**Args**: +- model_objs: A list of model objects that are already stored in the database. +- fields_to_update: A list of fields to update in the models. Only these fields will be updated in the database. The 'id' field is included by default. + +**Examples**: + + # Create a couple test models + model_obj1 = TestModel.objects.create(int_field=1, float_field=2.0, char_field='Hi') + model_obj2 = TestModel.objects.create(int_field=3, float_field=4.0, char_field='Hello') + + # Change their fields and do a bulk update + model_obj1.int_field = 10 + model_obj1.float_field = 20.0 + model_obj2.int_field = 30 + model_obj2.float_field = 40.0 + TestModel.objects.bulk_update([model_obj1, model_obj2], ['int_field', 'float_field']) + + # Reload the models and view their changes + model_obj1 = TestModel.objects.get(id=model_obj1.id) + print model_obj1.int_field, model_obj1.float_field + 10, 20.0 + + model_obj2 = TestModel.objects.get(id=model_obj2.id) + print model_obj2.int_field, model_obj2.float_field + 10, 20.0 + +## License +MIT License (See the LICENSE file included in this repository) diff --git a/manager_utils/manager_utils.py b/manager_utils/manager_utils.py index 56444ed..7104094 100644 --- a/manager_utils/manager_utils.py +++ b/manager_utils/manager_utils.py @@ -45,6 +45,27 @@ def bulk_update(self, model_objs, fields_to_update): Args: model_objs: A list of model objects that have been updated. fields_to_update: A list of fields to be updated. Only these fields will be updated + + Examples: + # Create a couple test models + model_obj1 = TestModel.objects.create(int_field=1, float_field=2.0, char_field='Hi') + model_obj2 = TestModel.objects.create(int_field=3, float_field=4.0, char_field='Hello') + + # Change their fields and do a bulk update + model_obj1.int_field = 10 + model_obj1.float_field = 20.0 + model_obj2.int_field = 30 + model_obj2.float_field = 40.0 + TestModel.objects.bulk_update([model_obj1, model_obj2], ['int_field', 'float_field']) + + # Reload the models and view their changes + model_obj1 = TestModel.objects.get(id=model_obj1.id) + print model_obj1.int_field, model_obj1.float_field + 10, 20.0 + + model_obj2 = TestModel.objects.get(id=model_obj2.id) + print model_obj2.int_field, model_obj2.float_field + 10, 20.0 """ updated_rows = [ [model_obj.id] + [getattr(model_obj, field_name) for field_name in fields_to_update] @@ -69,10 +90,39 @@ def upsert(self, defaults=None, updates=None, **kwargs): updates: These values are updated when the object is updated. They also override any values provided in the defaults when inserting the object. **kwargs: These values provide the arguments used when checking for the existence of - the object. - - Returns: - A tuple of the upserted object and a Boolean that is True if it was created (False otherwise) + the object. They are used in a similar manner to Django's get_or_create function. + + Returns: A tuple of the upserted object and a Boolean that is True if it was created (False otherwise) + + Examples: + # Upsert a test model with an int value of 1. Use default values that will be given to it when created + model_obj, created = TestModel.objects.upsert(int_field=1, defaults={'float_field': 2.0}) + print created + True + print model_obj.int_field, model_obj.float_field + 1, 2.0 + + # Do an upsert on that same model with different default fields. Since it already exists, the defaults + # are not used + model_obj, created = TestModel.objects.upsert(int_field=1, defaults={'float_field': 3.0}) + print created + False + print model_obj.int_field, model_obj.float_field + 1, 2.0 + + # In order to update the float field in an existing object, use the updates dictionary + model_obj, created = TestModel.objects.upsert(int_field=1, updates={'float_field': 3.0}) + print created + False + print model_obj.int_field, model_obj.float_field + 1, 3.0 + + # You can use updates on a newly created object that will also be used as initial values. + model_obj, created = TestModel.objects.upsert(int_field=2, updates={'float_field': 4.0}) + print created + True + print model_obj.int_field, model_obj.float_field + 2, 4.0 """ obj, created = self.model.objects.get_or_create(defaults=defaults or {}, **kwargs) @@ -87,8 +137,20 @@ def get_or_none(self, **query_params): """ Get an object or return None if it doesn't exist. - Returns: - A model object if one exists with the query params, None otherwise. + Args: + **query_params: The query parameters used in the lookup. + + Returns: A model object if one exists with the query params, None otherwise. + + Examples: + model_obj = TestModel.objects.get_or_none(int_field=1) + print model_obj + None + + TestModel.objects.create(int_field=1) + model_obj = TestModel.objects.get_or_none(int_field=1) + print model_obj.int_field + 1 """ return self.get_queryset().get_or_none(**query_params) @@ -96,6 +158,17 @@ def single(self): """ Assumes that this model only has one element in the table and returns it. If the table has more than one or no value, an exception is raised. + + Returns: The only model object in the queryset. + + Raises: DoesNotExist error when the object does not exist or a MultipleObjectsReturned error when there + is more than one object. + + Examples: + TestModel.objects.create(int_field=1) + model_obj = TestModel.objects.single() + print model_obj.int_field + 1 """ return self.get_queryset().single() From 876c25d6f5222b8253bd37f767e5eafeb2f4b137 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:26:23 -0500 Subject: [PATCH 08/24] fixed minor mistakes in the docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6160ef5..982954e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Django manager utils allows a user to perform various functions not natively sup ## single() -Assumes that the model only has one element in the table or queryset and returns that value. If the table has more than one or no value, an iexception is raised. +Assumes that the model only has one element in the table or queryset and returns that value. If the table has more than one or no value, an exception is raised. **Returns**: The only model object in the queryset. @@ -52,7 +52,7 @@ Performs an update on an object or an insert if the object does not exist. **Args**: - defaults: These values are set when the object is inserted, but are irrelevant when the object already exists. This field should only be used when values only need to be set during creation. - updates: These values are updated when the object is updated. They also override any values provided in the defaults when inserting the object. -- \*\*kwargs: These values provide the arguments used when checking for the existence of the object. They are used in a similar manner to Django's get_or_create function. +- \*\*kwargs: These values provide the arguments used when checking for the existence of the object. They are used in a similar manner to Django's get_or_create function and are set in a created object. **Returns**: A tuple of the upserted object and a Boolean that is True if it was created (False otherwise) From cdf7d481a1619f4962bf2de7641705cbd7b0a76a Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:31:34 -0500 Subject: [PATCH 09/24] added instructions on using the manager utils manager --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 982954e..eb8a8ba 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,21 @@ django-manager-utils Additional utilities for Django model managers. ## A Brief Overview -Django manager utils allows a user to perform various functions not natively supported by Django's model managers. A function overview is below with links to more in-depth documentation and examples for each function. +Django manager utils allows a user to perform various functions not natively supported by Django's model managers. To use the manager in your Django models, do: + + from manager_utils import ManagerUtilsManager + + class MyModel(Model): + objects = ManagerUtilsManager() + +If you want to extend an existing manager to use the manager utils, include mixin provided first (since it overrides the get_queryset function) as follows: + + from manager_utils import ManagerUtilsMixin + + class MyManager(ManagerUtilsMixin, Manager): + pass + +An overview of each util is below with links to more in-depth documentation and examples for each function. - [single](#single): Grabs a single element from table and verifies it is the only element. - [get_or_none](#get_or_none): Performs a get on a queryset and returns None if the object does not exist. From ca24c443d985d6ca0dbb857ebc53e1f345b98728 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:33:19 -0500 Subject: [PATCH 10/24] dropped support for django 1.4 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8162140..bae2a6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python python: - '2.7' env: - - DJANGO=1.4 DB=postgres - DJANGO=1.5 DB=postgres - DJANGO=1.6.1 DB=postgres install: From 43648f4f74d74903ecde361e29993f2c4c5a9653 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:34:16 -0500 Subject: [PATCH 11/24] added django 1.5 support in setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 020a435..4da15f2 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ 'git+https://github.com/wesokes/django-query-builder.git@0.5.2', ], install_requires=[ - 'django>=1.6', + 'django>=1.5', ], include_package_data=True, ) From 3a7980ca3ec86567fb6ff70893afa6a813824e83 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:35:03 -0500 Subject: [PATCH 12/24] removed celery from test project --- test_project/settings.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test_project/settings.py b/test_project/settings.py index 45fe16e..1879fd4 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -14,10 +14,6 @@ MANAGERS = ADMINS -# Setup celery -import djcelery -djcelery.setup_loader() - # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' DATABASES = { 'default': { From 00223de126beb6bd5723bd911b8e51384dd5e6db Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:42:00 -0500 Subject: [PATCH 13/24] removed south --- test_project/settings.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test_project/settings.py b/test_project/settings.py index 1879fd4..68a0bea 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -5,8 +5,6 @@ # Use the nose tests runner TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' -# Disable south in testing -SOUTH_TESTS_MIGRATE = False ADMINS = ( # ('Your Name', 'your_email@example.com'), @@ -127,7 +125,6 @@ 'entity', 'django_nose', 'djcelery', - 'south', 'test_project', ) From 5e1fd740de07ceb7d4881b86cff720f90b9dfe12 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:47:36 -0500 Subject: [PATCH 14/24] added south --- requirements.txt | 1 + test_project/settings.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f32d5bb..6b0cf2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ django-nose==1.1 pep8 psycopg2==2.4.5 pyflakes +south==0.7.6 git+https://github.com/wesokes/django-query-builder.git@0.5.2 # Note that Django is a requirement, but it is installed in the .travis.yml file in order to test against different versions diff --git a/test_project/settings.py b/test_project/settings.py index 1879fd4..2146846 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -126,7 +126,6 @@ 'django.contrib.admin', 'entity', 'django_nose', - 'djcelery', 'south', 'test_project', ) From 5893289f6702a4a34d89f48fc3bcf0346e5113e0 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:51:53 -0500 Subject: [PATCH 15/24] removed entity app --- test_project/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test_project/settings.py b/test_project/settings.py index e70768e..a05ed97 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -122,7 +122,6 @@ # 'django.contrib.messages', # 'django.contrib.staticfiles', 'django.contrib.admin', - 'entity', 'django_nose', 'south', 'test_project', From 3d0f5b12bb6d62162aa90ccfa3b14b90c624f9e7 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:56:02 -0500 Subject: [PATCH 16/24] changed backend to sqlite for travis --- test_project/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_project/settings.py b/test_project/settings.py index a05ed97..a49d535 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -15,7 +15,7 @@ # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'ENGINE': 'django.db.backends.sqlite3', #postgresql_psycopg2', 'NAME': 'ambition_dev', 'USER': 'ambition_dev', 'PASSWORD': 'ambition_dev', From 7b7a56ba7c44828a8d7ce50022dc795969b2ed92 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 00:58:11 -0500 Subject: [PATCH 17/24] changed backend to sqlite for travis --- test_project/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_project/settings.py b/test_project/settings.py index a49d535..5afe8cd 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -15,7 +15,7 @@ # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', #postgresql_psycopg2', + 'ENGINE': 'django.db.backends.sqlite3', # postgresql_psycopg2', 'NAME': 'ambition_dev', 'USER': 'ambition_dev', 'PASSWORD': 'ambition_dev', From 7c69a1f929c7196c482e694a76bec87a2e0c9787 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 01:07:28 -0500 Subject: [PATCH 18/24] changed travis postgres settings --- test_project/settings.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/test_project/settings.py b/test_project/settings.py index 5afe8cd..b370b48 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -1,4 +1,4 @@ -# Django settings for test_project project. +import os DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -13,15 +13,25 @@ MANAGERS = ADMINS # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # postgresql_psycopg2', - 'NAME': 'ambition_dev', - 'USER': 'ambition_dev', - 'PASSWORD': 'ambition_dev', - 'HOST': 'localhost' +test_db = os.environ.get('DB', None) +if test_db is not None: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'USER': 'postgres', + 'NAME': 'manager_utils', + } + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'ambition_dev', + 'USER': 'ambition_dev', + 'PASSWORD': 'ambition_dev', + 'HOST': 'localhost' + } } -} # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name From d4c51a330194c981e0d0888ddca160ae866be7c8 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 01:13:30 -0500 Subject: [PATCH 19/24] updated travis postgres settings --- .travis.yml | 3 +++ test_project/settings.py | 28 ++++++++-------------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index bae2a6a..c03470d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,9 @@ install: before_script: - find . | grep .py$ | grep -v /migrations | xargs pep8 --max-line-length=120 - find . | grep .py$ | grep -v /migrations | grep -v __init__.py | xargs pyflakes + - psql -c 'CREATE USER ambition_dev WITH PASSWORD "ambition_dev";' -U postgres + - psql -c 'CREATE DATABASE ambition_dev;' -U postgres + - psql -c 'GRANT ALL PRIVILEGES ON DATABASE ambition_dev TO ambition_dev;' -U postgres script: - coverage run --source='manager_utils' --branch manage.py test - coverage report --fail-under=100 diff --git a/test_project/settings.py b/test_project/settings.py index b370b48..ff36f8b 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -1,5 +1,3 @@ -import os - DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -13,25 +11,15 @@ MANAGERS = ADMINS # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' -test_db = os.environ.get('DB', None) -if test_db is not None: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'USER': 'postgres', - 'NAME': 'manager_utils', - } - } -else: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'ambition_dev', - 'USER': 'ambition_dev', - 'PASSWORD': 'ambition_dev', - 'HOST': 'localhost' - } +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'ambition_dev', + 'USER': 'ambition_dev', + 'PASSWORD': 'ambition_dev', + 'HOST': 'localhost' } +} # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name From e0cd19f267e12db0729a38b8b964be99a8fd2edc Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 01:16:48 -0500 Subject: [PATCH 20/24] updated travis postgres settings --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c03470d..fc0a866 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ install: before_script: - find . | grep .py$ | grep -v /migrations | xargs pep8 --max-line-length=120 - find . | grep .py$ | grep -v /migrations | grep -v __init__.py | xargs pyflakes - - psql -c 'CREATE USER ambition_dev WITH PASSWORD "ambition_dev";' -U postgres + - psql -c "CREATE USER ambition_dev WITH PASSWORD 'ambition_dev';" -U postgres - psql -c 'CREATE DATABASE ambition_dev;' -U postgres - psql -c 'GRANT ALL PRIVILEGES ON DATABASE ambition_dev TO ambition_dev;' -U postgres script: From 501661cd52c40bdf5ff4167b36cb2000f3a1c338 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 01:21:16 -0500 Subject: [PATCH 21/24] updated travis postgres settings --- .travis.yml | 4 +--- test_project/settings.py | 30 +++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc0a866..8581670 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,7 @@ install: before_script: - find . | grep .py$ | grep -v /migrations | xargs pep8 --max-line-length=120 - find . | grep .py$ | grep -v /migrations | grep -v __init__.py | xargs pyflakes - - psql -c "CREATE USER ambition_dev WITH PASSWORD 'ambition_dev';" -U postgres - - psql -c 'CREATE DATABASE ambition_dev;' -U postgres - - psql -c 'GRANT ALL PRIVILEGES ON DATABASE ambition_dev TO ambition_dev;' -U postgres + - psql -c 'CREATE DATABASE manager_utils;' -U postgres script: - coverage run --source='manager_utils' --branch manage.py test - coverage report --fail-under=100 diff --git a/test_project/settings.py b/test_project/settings.py index ff36f8b..746ddcc 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -1,3 +1,6 @@ +import os + + DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -10,16 +13,25 @@ MANAGERS = ADMINS -# Add 'postgresql_psycopg2', 'mysql', 'sqlite3' -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'ambition_dev', - 'USER': 'ambition_dev', - 'PASSWORD': 'ambition_dev', - 'HOST': 'localhost' +test_db = os.environ.get('DB', None) +if test_db is not None: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'USER': 'postgres', + 'NAME': 'modeltranslation', + } + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'ambition_dev', + 'USER': 'ambition_dev', + 'PASSWORD': 'ambition_dev', + 'HOST': 'localhost' + } } -} # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name From 722f0eb5a89b0c38a542e9df3cc88f25e577aa33 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 01:21:34 -0500 Subject: [PATCH 22/24] updated travis postgres settings --- test_project/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_project/settings.py b/test_project/settings.py index 746ddcc..9617399 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -19,7 +19,7 @@ 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'USER': 'postgres', - 'NAME': 'modeltranslation', + 'NAME': 'manger_utils', } } else: From cef27b6f5dd40c91c637e97d3302b2f0c4dadb45 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 01:22:44 -0500 Subject: [PATCH 23/24] updated travis postgres settings --- test_project/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_project/settings.py b/test_project/settings.py index 9617399..0d27616 100644 --- a/test_project/settings.py +++ b/test_project/settings.py @@ -19,7 +19,7 @@ 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'USER': 'postgres', - 'NAME': 'manger_utils', + 'NAME': 'manager_utils', } } else: From 50cccd4a0ef88515c687cb6192162638a100ca10 Mon Sep 17 00:00:00 2001 From: Wes Kendall Date: Thu, 6 Mar 2014 01:24:55 -0500 Subject: [PATCH 24/24] dropped support for django 1.5 --- .travis.yml | 1 - setup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8581670..83874d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python python: - '2.7' env: - - DJANGO=1.5 DB=postgres - DJANGO=1.6.1 DB=postgres install: - pip install -q Django==$DJANGO diff --git a/setup.py b/setup.py index 4da15f2..020a435 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ 'git+https://github.com/wesokes/django-query-builder.git@0.5.2', ], install_requires=[ - 'django>=1.5', + 'django>=1.6', ], include_package_data=True, )