/
__init__.py
1991 lines (1768 loc) · 93.3 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from django.conf import settings
from django.core import formfields, validators
from django.core import db
from django.core.exceptions import ObjectDoesNotExist
from django.core.meta.fields import *
from django.utils.functional import curry
from django.utils.text import capfirst
import copy, datetime, os, re, sys, types
# Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3
# Size of each "chunk" for get_iterator calls.
# Larger values are slightly faster at the expense of more storage space.
GET_ITERATOR_CHUNK_SIZE = 100
# Prefix (in Python path style) to location of models.
MODEL_PREFIX = 'django.models'
# Methods on models with the following prefix will be removed and
# converted to module-level functions.
MODEL_FUNCTIONS_PREFIX = '_module_'
# Methods on models with the following prefix will be removed and
# converted to manipulator methods.
MANIPULATOR_FUNCTIONS_PREFIX = '_manipulator_'
LOOKUP_SEPARATOR = '__'
####################
# HELPER FUNCTIONS #
####################
# Django currently supports two forms of ordering.
# Form 1 (deprecated) example:
# order_by=(('pub_date', 'DESC'), ('headline', 'ASC'), (None, 'RANDOM'))
# Form 2 (new-style) example:
# order_by=('-pub_date', 'headline', '?')
# Form 1 is deprecated and will no longer be supported for Django's first
# official release. The following code converts from Form 1 to Form 2.
LEGACY_ORDERING_MAPPING = {'ASC': '_', 'DESC': '-_', 'RANDOM': '?'}
def handle_legacy_orderlist(order_list):
if not order_list or isinstance(order_list[0], basestring):
return order_list
else:
import warnings
new_order_list = [LEGACY_ORDERING_MAPPING[j.upper()].replace('_', str(i)) for i, j in order_list]
warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning)
return new_order_list
def orderfield2column(f, opts):
try:
return opts.get_field(f, False).column
except FieldDoesNotExist:
return f
def orderlist2sql(order_list, opts, prefix=''):
if prefix.endswith('.'):
prefix = db.db.quote_name(prefix[:-1]) + '.'
output = []
for f in handle_legacy_orderlist(order_list):
if f.startswith('-'):
output.append('%s%s DESC' % (prefix, db.db.quote_name(orderfield2column(f[1:], opts))))
elif f == '?':
output.append(db.get_random_function_sql())
else:
output.append('%s%s ASC' % (prefix, db.db.quote_name(orderfield2column(f, opts))))
return ', '.join(output)
def get_module(app_label, module_name):
return __import__('%s.%s.%s' % (MODEL_PREFIX, app_label, module_name), '', '', [''])
def get_app(app_label):
return __import__('%s.%s' % (MODEL_PREFIX, app_label), '', '', [''])
_installed_models_cache = None
def get_installed_models():
"""
Returns a list of installed "models" packages, such as foo.models,
ellington.news.models, etc. This does NOT include django.models.
"""
global _installed_models_cache
if _installed_models_cache is not None:
return _installed_models_cache
_installed_models_cache = []
for a in settings.INSTALLED_APPS:
try:
_installed_models_cache.append(__import__(a + '.models', '', '', ['']))
except ImportError:
pass
return _installed_models_cache
_installed_modules_cache = None
def get_installed_model_modules(core_models=None):
"""
Returns a list of installed models, such as django.models.core,
ellington.news.models.news, foo.models.bar, etc.
"""
global _installed_modules_cache
if _installed_modules_cache is not None:
return _installed_modules_cache
_installed_modules_cache = []
# django.models is a special case.
for submodule in (core_models or []):
_installed_modules_cache.append(__import__('django.models.%s' % submodule, '', '', ['']))
for m in get_installed_models():
for submodule in getattr(m, '__all__', []):
mod = __import__('django.models.%s' % submodule, '', '', [''])
try:
mod._MODELS
except AttributeError:
pass # Skip model modules that don't actually have models in them.
else:
_installed_modules_cache.append(mod)
return _installed_modules_cache
class LazyDate:
"""
Use in limit_choices_to to compare the field to dates calculated at run time
instead of when the model is loaded. For example::
... limit_choices_to = {'date__gt' : meta.LazyDate(days=-3)} ...
which will limit the choices to dates greater than three days ago.
"""
def __init__(self, **kwargs):
self.delta = datetime.timedelta(**kwargs)
def __str__(self):
return str(self.__get_value__())
def __repr__(self):
return "<LazyDate: %s>" % self.delta
def __get_value__(self):
return datetime.datetime.now() + self.delta
################
# MAIN CLASSES #
################
class FieldDoesNotExist(Exception):
pass
class BadKeywordArguments(Exception):
pass
class BoundRelatedObject(object):
def __init__(self, related_object, field_mapping, original):
self.relation = related_object
self.field_mappings = field_mapping[related_object.name]
def template_name(self):
raise NotImplementedError
def __repr__(self):
return repr(self.__dict__)
class RelatedObject(object):
def __init__(self, parent_opts, opts, field):
self.parent_opts = parent_opts
self.opts = opts
self.field = field
self.edit_inline = field.rel.edit_inline
self.name = '%s_%s' % (opts.app_label, opts.module_name)
self.var_name = opts.object_name.lower()
def flatten_data(self, follow, obj=None):
new_data = {}
rel_instances = self.get_list(obj)
for i, rel_instance in enumerate(rel_instances):
instance_data = {}
for f in self.opts.fields + self.opts.many_to_many:
# TODO: Fix for recursive manipulators.
fol = follow.get(f.name, None)
if fol:
field_data = f.flatten_data(fol, rel_instance)
for name, value in field_data.items():
instance_data['%s.%d.%s' % (self.var_name, i, name)] = value
new_data.update(instance_data)
return new_data
def extract_data(self, data):
"""
Pull out the data meant for inline objects of this class,
i.e. anything starting with our module name.
"""
return data # TODO
def get_list(self, parent_instance=None):
"Get the list of this type of object from an instance of the parent class."
if parent_instance != None:
func_name = 'get_%s_list' % self.get_method_name_part()
func = getattr(parent_instance, func_name)
list = func()
count = len(list) + self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin:
count = max(count, self.field.rel.min_num_in_admin)
if self.field.rel.max_num_in_admin:
count = min(count, self.field.rel.max_num_in_admin)
change = count - len(list)
if change > 0:
return list + [None] * change
if change < 0:
return list[:change]
else: # Just right
return list
else:
return [None] * self.field.rel.num_in_admin
def editable_fields(self):
"Get the fields in this class that should be edited inline."
return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
def get_follow(self, override=None):
if isinstance(override, bool):
if override:
over = {}
else:
return None
else:
if override:
over = override.copy()
elif self.edit_inline:
over = {}
else:
return None
over[self.field.name] = False
return self.opts.get_follow(over)
def __repr__(self):
return "<RelatedObject: %s related to %s>" % ( self.name, self.field.name)
def get_manipulator_fields(self, opts, manipulator, change, follow):
# TODO: Remove core fields stuff.
if change:
meth_name = 'get_%s_count' % self.get_method_name_part()
count = getattr(manipulator.original_object, meth_name)()
count += self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin:
count = max(count, self.field.rel.min_num_in_admin)
if self.field.rel.max_num_in_admin:
count = min(count, self.field.rel.max_num_in_admin)
else:
count = self.field.rel.num_in_admin
fields = []
for i in range(count):
for f in self.opts.fields + self.opts.many_to_many:
if follow.get(f.name, False):
prefix = '%s.%d.' % (self.var_name, i)
fields.extend(f.get_manipulator_fields(self.opts, manipulator, change, name_prefix=prefix, rel=True))
return fields
def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
return bound_related_object_class(self, field_mapping, original)
def get_method_name_part(self):
# This method encapsulates the logic that decides what name to give a
# method that retrieves related many-to-one or many-to-many objects.
# Usually it just uses the lower-cased object_name, but if the related
# object is in another app, the related object's app_label is appended.
#
# Examples:
#
# # Normal case -- a related object in the same app.
# # This method returns "choice".
# Poll.get_choice_list()
#
# # A related object in a different app.
# # This method returns "lcom_bestofaward".
# Place.get_lcom_bestofaward_list() # "lcom_bestofaward"
rel_obj_name = self.field.rel.related_name or self.opts.object_name.lower()
if self.parent_opts.app_label != self.opts.app_label:
rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name)
return rel_obj_name
class QBase:
"Base class for QAnd and QOr"
def __init__(self, *args):
self.args = args
def __repr__(self):
return '(%s)' % self.operator.join([repr(el) for el in self.args])
def get_sql(self, opts, table_count):
tables, join_where, where, params = [], [], [], []
for val in self.args:
tables2, join_where2, where2, params2, table_count = val.get_sql(opts, table_count)
tables.extend(tables2)
join_where.extend(join_where2)
where.extend(where2)
params.extend(params2)
return tables, join_where, ['(%s)' % self.operator.join(where)], params, table_count
class QAnd(QBase):
"Encapsulates a combined query that uses 'AND'."
operator = ' AND '
def __or__(self, other):
if isinstance(other, (QAnd, QOr, Q)):
return QOr(self, other)
else:
raise TypeError, other
def __and__(self, other):
if isinstance(other, QAnd):
return QAnd(*(self.args+other.args))
elif isinstance(other, (Q, QOr)):
return QAnd(*(self.args+(other,)))
else:
raise TypeError, other
class QOr(QBase):
"Encapsulates a combined query that uses 'OR'."
operator = ' OR '
def __and__(self, other):
if isinstance(other, (QAnd, QOr, Q)):
return QAnd(self, other)
else:
raise TypeError, other
def __or__(self, other):
if isinstance(other, QOr):
return QOr(*(self.args+other.args))
elif isinstance(other, (Q, QAnd)):
return QOr(*(self.args+(other,)))
else:
raise TypeError, other
class Q:
"Encapsulates queries for the 'complex' parameter to Django API functions."
def __init__(self, **kwargs):
self.kwargs = kwargs
def __repr__(self):
return 'Q%r' % self.kwargs
def __and__(self, other):
if isinstance(other, (Q, QAnd, QOr)):
return QAnd(self, other)
else:
raise TypeError, other
def __or__(self, other):
if isinstance(other, (Q, QAnd, QOr)):
return QOr(self, other)
else:
raise TypeError, other
def get_sql(self, opts, table_count):
return _parse_lookup(self.kwargs.items(), opts, table_count)
class Options:
def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
where_constraints=None, object_name=None, app_label=None,
exceptions=None, permissions=None, get_latest_by=None,
order_with_respect_to=None, module_constants=None):
# Save the original function args, for use by copy(). Note that we're
# NOT using copy.deepcopy(), because that would create a new copy of
# everything in memory, and it's better to conserve memory. Of course,
# this comes with the important gotcha that changing any attribute of
# this object will change its value in self._orig_init_args, so we
# need to be careful not to do that. In practice, we can pull this off
# because Options are generally read-only objects, and __init__() is
# the only place where its attributes are manipulated.
# locals() is used purely for convenience, so we don't have to do
# something verbose like this:
# self._orig_init_args = {
# 'module_name': module_name,
# 'verbose_name': verbose_name,
# ...
# }
self._orig_init_args = locals()
del self._orig_init_args['self'] # because we don't care about it.
# Move many-to-many related fields from self.fields into self.many_to_many.
self.fields, self.many_to_many = [], []
for field in (fields or []):
if field.rel and isinstance(field.rel, ManyToManyRel):
self.many_to_many.append(field)
else:
self.fields.append(field)
self.module_name, self.verbose_name = module_name, verbose_name
self.verbose_name_plural = verbose_name_plural or verbose_name + 's'
self.db_table, self.has_related_links = db_table, has_related_links
self.ordering = ordering or []
self.unique_together = unique_together or []
self.where_constraints = where_constraints or []
self.exceptions = exceptions or []
self.permissions = permissions or []
self.object_name, self.app_label = object_name, app_label
self.get_latest_by = get_latest_by
if order_with_respect_to:
self.order_with_respect_to = self.get_field(order_with_respect_to)
self.ordering = ('_order',)
else:
self.order_with_respect_to = None
self.module_constants = module_constants or {}
self.admin = admin
# Calculate one_to_one_field.
self.one_to_one_field = None
for f in self.fields:
if isinstance(f.rel, OneToOneRel):
self.one_to_one_field = f
break
# Cache the primary-key field.
self.pk = None
for f in self.fields:
if f.primary_key:
self.pk = f
break
# If a primary_key field hasn't been specified, add an
# auto-incrementing primary-key ID field automatically.
if self.pk is None:
self.fields.insert(0, AutoField(name='id', verbose_name='ID', primary_key=True))
self.pk = self.fields[0]
# Cache whether this has an AutoField.
self.has_auto_field = False
for f in self.fields:
is_auto = isinstance(f, AutoField)
if is_auto and self.has_auto_field:
raise AssertionError, "A model can't have more than one AutoField."
elif is_auto:
self.has_auto_field = True
def __repr__(self):
return '<Options for %s>' % self.module_name
def copy(self, **kwargs):
args = self._orig_init_args.copy()
args.update(kwargs)
return self.__class__(**args)
def get_model_module(self):
return get_module(self.app_label, self.module_name)
def get_content_type_id(self):
"Returns the content-type ID for this object type."
if not hasattr(self, '_content_type_id'):
mod = get_module('core', 'contenttypes')
self._content_type_id = mod.get_object(python_module_name__exact=self.module_name, package__label__exact=self.app_label).id
return self._content_type_id
def get_field(self, name, many_to_many=True):
"""
Returns the requested field by name. Raises FieldDoesNotExist on error.
"""
to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
for f in to_search:
if f.name == name:
return f
raise FieldDoesNotExist, "name=%s" % name
def get_order_sql(self, table_prefix=''):
"Returns the full 'ORDER BY' clause for this object, according to self.ordering."
if not self.ordering: return ''
pre = table_prefix and (table_prefix + '.') or ''
return 'ORDER BY ' + orderlist2sql(self.ordering, self, pre)
def get_add_permission(self):
return 'add_%s' % self.object_name.lower()
def get_change_permission(self):
return 'change_%s' % self.object_name.lower()
def get_delete_permission(self):
return 'delete_%s' % self.object_name.lower()
def get_all_related_objects(self):
try: # Try the cache first.
return self._all_related_objects
except AttributeError:
module_list = get_installed_model_modules()
rel_objs = []
for mod in module_list:
for klass in mod._MODELS:
for f in klass._meta.fields:
if f.rel and self == f.rel.to:
rel_objs.append(RelatedObject(self, klass._meta, f))
if self.has_related_links:
# Manually add RelatedLink objects, which are a special case.
relatedlinks = get_module('relatedlinks', 'relatedlinks')
# Note that the copy() is very important -- otherwise any
# subsequently loaded object with related links will override this
# relationship we're adding.
link_field = copy.copy(relatedlinks.RelatedLink._meta.get_field('object_id'))
link_field.rel = ManyToOneRel(self.get_model_module().Klass, 'id',
num_in_admin=3, min_num_in_admin=3, edit_inline=TABULAR,
lookup_overrides={
'content_type__package__label__exact': self.app_label,
'content_type__python_module_name__exact': self.module_name,
})
rel_objs.append(RelatedObject(self, relatedlinks.RelatedLink._meta, link_field))
self._all_related_objects = rel_objs
return rel_objs
def get_followed_related_objects(self, follow=None):
if follow == None:
follow = self.get_follow()
return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
def get_data_holders(self, follow=None):
if follow == None:
follow = self.get_follow()
return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)]
def get_follow(self, override=None):
follow = {}
for f in self.fields + self.many_to_many + self.get_all_related_objects():
if override and override.has_key(f.name):
child_override = override[f.name]
else:
child_override = None
fol = f.get_follow(child_override)
if fol:
follow[f.name] = fol
return follow
def get_all_related_many_to_many_objects(self):
module_list = get_installed_model_modules()
rel_objs = []
for mod in module_list:
for klass in mod._MODELS:
for f in klass._meta.many_to_many:
if f.rel and self == f.rel.to:
rel_objs.append(RelatedObject(self, klass._meta, f))
return rel_objs
def get_ordered_objects(self):
"Returns a list of Options objects that are ordered with respect to this object."
if not hasattr(self, '_ordered_objects'):
objects = []
for klass in get_app(self.app_label)._MODELS:
opts = klass._meta
if opts.order_with_respect_to and opts.order_with_respect_to.rel \
and self == opts.order_with_respect_to.rel.to:
objects.append(opts)
self._ordered_objects = objects
return self._ordered_objects
def has_field_type(self, field_type, follow=None):
"""
Returns True if this object's admin form has at least one of the given
field_type (e.g. FileField).
"""
# TODO: follow
if not hasattr(self, '_field_types'):
self._field_types = {}
if not self._field_types.has_key(field_type):
try:
# First check self.fields.
for f in self.fields:
if isinstance(f, field_type):
raise StopIteration
# Failing that, check related fields.
for related in self.get_followed_related_objects(follow):
for f in related.opts.fields:
if isinstance(f, field_type):
raise StopIteration
except StopIteration:
self._field_types[field_type] = True
else:
self._field_types[field_type] = False
return self._field_types[field_type]
def _reassign_globals(function_dict, extra_globals, namespace):
new_functions = {}
for k, v in function_dict.items():
# Get the code object.
code = v.func_code
# Recreate the function, but give it access to extra_globals and the
# given namespace's globals, too.
new_globals = {'__builtins__': __builtins__, 'db': db.db, 'datetime': datetime}
new_globals.update(extra_globals.__dict__)
func = types.FunctionType(code, globals=new_globals, name=k, argdefs=v.func_defaults)
func.__dict__.update(v.__dict__)
setattr(namespace, k, func)
# For all of the custom functions that have been added so far, give
# them access to the new function we've just created.
for new_k, new_v in new_functions.items():
new_v.func_globals[k] = func
new_functions[k] = func
# Calculate the module_name using a poor-man's pluralization.
get_module_name = lambda class_name: class_name.lower() + 's'
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
get_verbose_name = lambda class_name: re.sub('([A-Z])', ' \\1', class_name).lower().strip()
class ModelBase(type):
"Metaclass for all models"
def __new__(cls, name, bases, attrs):
# If this isn't a subclass of Model, don't do anything special.
if not bases:
return type.__new__(cls, name, bases, attrs)
try:
meta_attrs = attrs.pop('META').__dict__
del meta_attrs['__module__']
del meta_attrs['__doc__']
except KeyError:
meta_attrs = {}
# Gather all attributes that are Field instances.
fields = []
for obj_name, obj in attrs.items():
if isinstance(obj, Field):
obj.set_name(obj_name)
fields.append(obj)
del attrs[obj_name]
# Sort the fields in the order that they were created. The
# "creation_counter" is needed because metaclasses don't preserve the
# attribute order.
fields.sort(lambda x, y: x.creation_counter - y.creation_counter)
# If this model is a subclass of another model, create an Options
# object by first copying the base class's _meta and then updating it
# with the overrides from this class.
replaces_module = None
if bases[0] != Model:
field_names = [f.name for f in fields]
remove_fields = meta_attrs.pop('remove_fields', [])
for f in bases[0]._meta._orig_init_args['fields']:
if f.name not in field_names and f.name not in remove_fields:
fields.insert(0, f)
if meta_attrs.has_key('replaces_module'):
# Set the replaces_module variable for now. We can't actually
# do anything with it yet, because the module hasn't yet been
# created.
replaces_module = meta_attrs.pop('replaces_module').split('.')
# Pass any Options overrides to the base's Options instance, and
# simultaneously remove them from attrs. When this is done, attrs
# will be a dictionary of custom methods, plus __module__.
meta_overrides = {'fields': fields, 'module_name': get_module_name(name), 'verbose_name': get_verbose_name(name)}
for k, v in meta_attrs.items():
if not callable(v) and k != '__module__':
meta_overrides[k] = meta_attrs.pop(k)
opts = bases[0]._meta.copy(**meta_overrides)
opts.object_name = name
del meta_overrides
else:
opts = Options(
module_name = meta_attrs.pop('module_name', get_module_name(name)),
# If the verbose_name wasn't given, use the class name,
# converted from InitialCaps to "lowercase with spaces".
verbose_name = meta_attrs.pop('verbose_name', get_verbose_name(name)),
verbose_name_plural = meta_attrs.pop('verbose_name_plural', ''),
db_table = meta_attrs.pop('db_table', ''),
fields = fields,
ordering = meta_attrs.pop('ordering', None),
unique_together = meta_attrs.pop('unique_together', None),
admin = meta_attrs.pop('admin', None),
has_related_links = meta_attrs.pop('has_related_links', False),
where_constraints = meta_attrs.pop('where_constraints', None),
object_name = name,
app_label = meta_attrs.pop('app_label', None),
exceptions = meta_attrs.pop('exceptions', None),
permissions = meta_attrs.pop('permissions', None),
get_latest_by = meta_attrs.pop('get_latest_by', None),
order_with_respect_to = meta_attrs.pop('order_with_respect_to', None),
module_constants = meta_attrs.pop('module_constants', None),
)
if meta_attrs != {}:
raise TypeError, "'class META' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
# Dynamically create the module that will contain this class and its
# associated helper functions.
if replaces_module is not None:
new_mod = get_module(*replaces_module)
else:
new_mod = types.ModuleType(opts.module_name)
# Collect any/all custom class methods and module functions, and move
# them to a temporary holding variable. We'll deal with them later.
if replaces_module is not None:
# Initialize these values to the base class' custom_methods and
# custom_functions.
custom_methods = dict([(k, v) for k, v in new_mod.Klass.__dict__.items() if hasattr(v, 'custom')])
custom_functions = dict([(k, v) for k, v in new_mod.__dict__.items() if hasattr(v, 'custom')])
else:
custom_methods, custom_functions = {}, {}
manipulator_methods = {}
for k, v in attrs.items():
if k in ('__module__', '__init__', '_overrides', '__doc__'):
continue # Skip the important stuff.
assert callable(v), "%r is an invalid model parameter." % k
# Give the function a function attribute "custom" to designate that
# it's a custom function/method.
v.custom = True
if k.startswith(MODEL_FUNCTIONS_PREFIX):
custom_functions[k[len(MODEL_FUNCTIONS_PREFIX):]] = v
elif k.startswith(MANIPULATOR_FUNCTIONS_PREFIX):
manipulator_methods[k[len(MANIPULATOR_FUNCTIONS_PREFIX):]] = v
else:
custom_methods[k] = v
del attrs[k]
# Create the module-level ObjectDoesNotExist exception.
dne_exc_name = '%sDoesNotExist' % name
does_not_exist_exception = types.ClassType(dne_exc_name, (ObjectDoesNotExist,), {})
# Explicitly set its __module__ because it will initially (incorrectly)
# be set to the module the code is being executed in.
does_not_exist_exception.__module__ = MODEL_PREFIX + '.' + opts.module_name
setattr(new_mod, dne_exc_name, does_not_exist_exception)
# Create other exceptions.
for exception_name in opts.exceptions:
exc = types.ClassType(exception_name, (Exception,), {})
exc.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
setattr(new_mod, exception_name, exc)
# Create any module-level constants, if applicable.
for k, v in opts.module_constants.items():
setattr(new_mod, k, v)
# Create the default class methods.
attrs['__init__'] = curry(method_init, opts)
attrs['__eq__'] = curry(method_eq, opts)
attrs['__ne__'] = curry(method_ne, opts)
attrs['save'] = curry(method_save, opts)
attrs['save'].alters_data = True
attrs['delete'] = curry(method_delete, opts)
attrs['delete'].alters_data = True
if opts.order_with_respect_to:
attrs['get_next_in_order'] = curry(method_get_next_in_order, opts, opts.order_with_respect_to)
attrs['get_previous_in_order'] = curry(method_get_previous_in_order, opts, opts.order_with_respect_to)
for f in opts.fields:
# If the object has a relationship to itself, as designated by
# RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
f.rel.to = opts
f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name)
f.verbose_name = f.verbose_name or f.rel.to.verbose_name
f.rel.field_name = f.rel.field_name or f.rel.to.pk.name
# Add "get_thingie" methods for many-to-one related objects.
# EXAMPLES: Choice.get_poll(), Story.get_dateline()
if isinstance(f.rel, ManyToOneRel):
func = curry(method_get_many_to_one, f)
func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name)
attrs['get_%s' % f.name] = func
for f in opts.many_to_many:
# Add "get_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
func = curry(method_get_many_to_many, f)
func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name)
attrs['get_%s_list' % f.rel.singular] = func
# Add "set_thingie" methods for many-to-many related objects.
# EXAMPLES: Poll.set_sites(), Story.set_bylines()
func = curry(method_set_many_to_many, f)
func.__doc__ = "Resets this object's `%s.%s` list to the given list of IDs. Note that it doesn't check whether the given IDs are valid." % (f.rel.to.app_label, f.rel.to.module_name)
func.alters_data = True
attrs['set_%s' % f.name] = func
# Create the class, because we need it to use in currying.
new_class = type.__new__(cls, name, bases, attrs)
# Give the class a docstring -- its definition.
if new_class.__doc__ is None:
new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields]))
# Create the standard, module-level API helper functions such
# as get_object() and get_list().
new_mod.get_object = curry(function_get_object, opts, new_class, does_not_exist_exception)
new_mod.get_object.__doc__ = "Returns the %s object matching the given parameters." % name
new_mod.get_list = curry(function_get_list, opts, new_class)
new_mod.get_list.__doc__ = "Returns a list of %s objects matching the given parameters." % name
new_mod.get_iterator = curry(function_get_iterator, opts, new_class)
new_mod.get_iterator.__doc__ = "Returns an iterator of %s objects matching the given parameters." % name
new_mod.get_values = curry(function_get_values, opts, new_class)
new_mod.get_values.__doc__ = "Returns a list of dictionaries matching the given parameters."
new_mod.get_values_iterator = curry(function_get_values_iterator, opts, new_class)
new_mod.get_values_iterator.__doc__ = "Returns an iterator of dictionaries matching the given parameters."
new_mod.get_count = curry(function_get_count, opts)
new_mod.get_count.__doc__ = "Returns the number of %s objects matching the given parameters." % name
new_mod._get_sql_clause = curry(function_get_sql_clause, opts)
new_mod.get_in_bulk = curry(function_get_in_bulk, opts, new_class)
new_mod.get_in_bulk.__doc__ = "Returns a dictionary of ID -> %s for the %s objects with IDs in the given id_list." % (name, name)
if opts.get_latest_by:
new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
for f in opts.fields:
#TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class.
if f.choices:
# Add "get_thingie_display" method to get human-readable value.
func = curry(method_get_display_value, f)
setattr(new_class, 'get_%s_display' % f.name, func)
if isinstance(f, DateField) or isinstance(f, DateTimeField):
# Add "get_next_by_thingie" and "get_previous_by_thingie" methods
# for all DateFields and DateTimeFields that cannot be null.
# EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date()
if not f.null:
setattr(new_class, 'get_next_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, True))
setattr(new_class, 'get_previous_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, False))
# Add "get_thingie_list" for all DateFields and DateTimeFields.
# EXAMPLE: polls.get_pub_date_list()
func = curry(function_get_date_list, opts, f)
func.__doc__ = "Returns a list of days, months or years (as datetime.datetime objects) in which %s objects are available. The first parameter ('kind') must be one of 'year', 'month' or 'day'." % name
setattr(new_mod, 'get_%s_list' % f.name, func)
elif isinstance(f, FileField):
setattr(new_class, 'get_%s_filename' % f.name, curry(method_get_file_filename, f))
setattr(new_class, 'get_%s_url' % f.name, curry(method_get_file_url, f))
setattr(new_class, 'get_%s_size' % f.name, curry(method_get_file_size, f))
func = curry(method_save_file, f)
func.alters_data = True
setattr(new_class, 'save_%s_file' % f.name, func)
if isinstance(f, ImageField):
# Add get_BLAH_width and get_BLAH_height methods, but only
# if the image field doesn't have width and height cache
# fields.
if not f.width_field:
setattr(new_class, 'get_%s_width' % f.name, curry(method_get_image_width, f))
if not f.height_field:
setattr(new_class, 'get_%s_height' % f.name, curry(method_get_image_height, f))
# Add the class itself to the new module we've created.
new_mod.__dict__[name] = new_class
# Add "Klass" -- a shortcut reference to the class.
new_mod.__dict__['Klass'] = new_class
# Add the Manipulators.
new_mod.__dict__['AddManipulator'] = get_manipulator(opts, new_class, manipulator_methods, add=True)
new_mod.__dict__['ChangeManipulator'] = get_manipulator(opts, new_class, manipulator_methods, change=True)
# Now that we have references to new_mod and new_class, we can add
# any/all extra class methods to the new class. Note that we could
# have just left the extra methods in attrs (above), but that would
# have meant that any code within the extra methods would *not* have
# access to module-level globals, such as get_list(), db, etc.
# In order to give these methods access to those globals, we have to
# deconstruct the method getting its raw "code" object, then recreating
# the function with a new "globals" dictionary.
#
# To complicate matters more, because each method is manually assigned
# a "globals" value, that "globals" value does NOT include the methods
# that haven't been created yet. For instance, if there are two custom
# methods, foo() and bar(), and foo() is created first, it won't have
# bar() within its globals(). This is a problem because sometimes
# custom methods/functions refer to other custom methods/functions. To
# solve this problem, we keep track of the new functions created (in
# the new_functions variable) and manually append each new function to
# the func_globals() of all previously-created functions. So, by the
# end of the loop, all functions will "know" about all the other
# functions.
_reassign_globals(custom_methods, new_mod, new_class)
_reassign_globals(custom_functions, new_mod, new_mod)
_reassign_globals(manipulator_methods, new_mod, new_mod.__dict__['AddManipulator'])
_reassign_globals(manipulator_methods, new_mod, new_mod.__dict__['ChangeManipulator'])
if hasattr(new_class, 'get_absolute_url'):
new_class.get_absolute_url = curry(get_absolute_url, opts, new_class.get_absolute_url)
# Get a reference to the module the class is in, and dynamically add
# the new module to it.
app_package = sys.modules.get(new_class.__module__)
if replaces_module is not None:
app_label = replaces_module[0]
else:
app_package.__dict__[opts.module_name] = new_mod
app_label = app_package.__name__[app_package.__name__.rfind('.')+1:]
# Populate the _MODELS member on the module the class is in.
# Example: django.models.polls will have a _MODELS member that will
# contain this list:
# [<class 'django.models.polls.Poll'>, <class 'django.models.polls.Choice'>]
# Don't do this if replaces_module is set.
app_package.__dict__.setdefault('_MODELS', []).append(new_class)
# Cache the app label.
opts.app_label = app_label
# If the db_table wasn't provided, use the app_label + module_name.
if not opts.db_table:
opts.db_table = "%s_%s" % (app_label, opts.module_name)
new_class._meta = opts
# Set the __file__ attribute to the __file__ attribute of its package,
# because they're technically from the same file. Note: if we didn't
# set this, sys.modules would think this module was built-in.
try:
new_mod.__file__ = app_package.__file__
except AttributeError:
# 'module' object has no attribute '__file__', which means the
# class was probably being entered via the interactive interpreter.
pass
# Add the module's entry to sys.modules -- for instance,
# "django.models.polls.polls". Note that "django.models.polls" has already
# been added automatically.
sys.modules.setdefault('%s.%s.%s' % (MODEL_PREFIX, app_label, opts.module_name), new_mod)
# If this module replaces another one, get a reference to the other
# module's parent, and replace the other module with the one we've just
# created.
if replaces_module is not None:
old_app = get_app(replaces_module[0])
setattr(old_app, replaces_module[1], new_mod)
for i, model in enumerate(old_app._MODELS):
if model._meta.module_name == replaces_module[1]:
# Replace the appropriate member of the old app's _MODELS
# data structure.
old_app._MODELS[i] = new_class
# Replace all relationships to the old class with
# relationships to the new one.
for related in model._meta.get_all_related_objects() + model._meta.get_all_related_many_to_many_objects():
related.field.rel.to = opts
break
return new_class
class Model:
__metaclass__ = ModelBase
def __repr__(self):
return '<%s object>' % self.__class__.__name__
############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
############################################
# CORE METHODS #############################
def method_init(opts, self, *args, **kwargs):
if kwargs:
for f in opts.fields:
if isinstance(f.rel, ManyToOneRel):
try:
# Assume object instance was passed in.
rel_obj = kwargs.pop(f.name)
except KeyError:
try:
# Object instance wasn't passed in -- must be an ID.
val = kwargs.pop(f.attname)
except KeyError:
val = f.get_default()
else:
# Object instance was passed in.
# Special case: You can pass in "None" for related objects if it's allowed.
if rel_obj is None and f.null:
val = None
else:
try:
val = getattr(rel_obj, f.rel.get_related_field().attname)
except AttributeError:
raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
setattr(self, f.attname, val)
else:
val = kwargs.pop(f.attname, f.get_default())
setattr(self, f.attname, val)
if kwargs:
raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
for i, arg in enumerate(args):
setattr(self, opts.fields[i].attname, arg)
def method_eq(opts, self, other):
return isinstance(other, self.__class__) and getattr(self, opts.pk.attname) == getattr(other, opts.pk.attname)
def method_ne(opts, self, other):
return not method_eq(opts, self, other)
def method_save(opts, self):
# Run any pre-save hooks.
if hasattr(self, '_pre_save'):
self._pre_save()
non_pks = [f for f in opts.fields if not f.primary_key]
cursor = db.db.cursor()
# First, try an UPDATE. If that doesn't update anything, do an INSERT.
pk_val = getattr(self, opts.pk.attname)
pk_set = bool(pk_val)
record_exists = True
if pk_set:
# Determine whether a record with the primary key already exists.
cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
(db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)), [pk_val])
# If it does already exist, do an UPDATE.
if cursor.fetchone():