21
21
# copy of django function without use of six
22
22
def python_2_unicode_compatible (klass ):
23
23
"""
24
- A decorator that defines __unicode__ and __str__ methods under Python 2.
25
- Under Python 3 it does nothing.
24
+ Decorator defining __unicode__ and __str__ as appropriate for Py2/3
26
25
27
- To support Python 2 and 3 with a single code base, define a __str__ method
28
- returning text and apply this decorator to the class.
26
+ Usage: define __str__ method and apply this decorator to the class.
29
27
"""
30
28
if sys .version_info [0 ] != 3 :
31
29
klass .__unicode__ = klass .__str__
@@ -43,14 +41,19 @@ def contribute_to_class(self, cls, name):
43
41
models .signals .class_prepared .connect (self .finalize , sender = cls )
44
42
45
43
def save_without_historical_record (self , * args , ** kwargs ):
46
- """Caution! Make sure you know what you're doing before you use this method."""
44
+ """
45
+ Save model without saving a historical record
46
+
47
+ Make sure you know what you're doing before you use this method.
48
+ """
47
49
self .skip_history_when_saving = True
48
50
try :
49
51
ret = self .save (* args , ** kwargs )
50
52
finally :
51
53
del self .skip_history_when_saving
52
54
return ret
53
- setattr (cls , 'save_without_historical_record' , save_without_historical_record )
55
+ setattr (cls , 'save_without_historical_record' ,
56
+ save_without_historical_record )
54
57
55
58
def finalize (self , sender , ** kwargs ):
56
59
history_model = self .create_history_model (sender )
@@ -99,100 +102,24 @@ def copy_fields(self, model):
99
102
a dictionary mapping field name to copied field object.
100
103
"""
101
104
fields = {}
102
-
103
- def customize_field (field ):
104
- field .name = field .attname
105
- if isinstance (field , models .AutoField ):
106
- # The historical model gets its own AutoField, so any
107
- # existing one must be replaced with an IntegerField.
108
- field .__class__ = models .IntegerField
109
- elif isinstance (field , models .FileField ):
110
- # Don't copy file, just path.
111
- field .__class__ = models .TextField
112
-
113
- # The historical instance should not change creation/modification timestamps.
114
- field .auto_now = False
115
- field .auto_now_add = False
116
-
117
- if field .primary_key or field .unique :
118
- # Unique fields can no longer be guaranteed unique,
119
- # but they should still be indexed for faster lookups.
120
- field .primary_key = False
121
- field ._unique = False
122
- field .db_index = True
123
- field .serialize = True
124
-
125
105
for field in model ._meta .fields :
126
106
field = copy .copy (field )
127
-
128
107
if isinstance (field , models .ForeignKey ):
129
- class CustomKey (type (field )):
130
-
131
- def get_attname (self ):
132
- return self .name
133
-
134
- def do_related_class (self , other , cls ):
135
- # this hooks into contribute_to_class() and this is
136
- # called specifically after the class_prepared signal
137
- to_field = copy .copy (self .rel .to ._meta .pk )
138
- field = self
139
- if isinstance (to_field , models .AutoField ):
140
- field .__class__ = models .IntegerField
141
- else :
142
- field .__class__ = to_field .__class__
143
- excluded_prefixes = ("_" ,"__" )
144
- excluded_attributes = (
145
- "rel" ,
146
- "creation_counter" ,
147
- "validators" ,
148
- "error_messages" ,
149
- "attname" ,
150
- "column" ,
151
- "help_text" ,
152
- "name" ,
153
- "model" ,
154
- "unique_for_year" ,
155
- "unique_for_date" ,
156
- "unique_for_month" ,
157
- "db_tablespace" ,
158
- "db_index" ,
159
- "db_column" ,
160
- "default" ,
161
- "auto_created" ,
162
- "null" ,
163
- "blank" ,
164
- )
165
- for key , val in to_field .__dict__ .items ():
166
- if (isinstance (key , basestring )
167
- and not key .startswith (excluded_prefixes )
168
- and not key in excluded_attributes ):
169
- setattr (field , key , val )
170
-
171
- customize_field (field )
172
- field .rel = None
173
-
174
- def contribute_to_class (self , cls , name ):
175
- # HACK: remove annoying descriptor (don't super())
176
- RelatedField .contribute_to_class (self , cls , name )
177
-
178
108
# Don't allow reverse relations.
179
109
# ForeignKey knows best what datatype to use for the column
180
110
# we'll used that as soon as it's finalized by copying rel.to
181
- field .__class__ = CustomKey
111
+ field .__class__ = get_custom_fk_class ( type ( field ))
182
112
field .rel .related_name = '+'
183
113
field .null = True
184
114
field .blank = True
185
-
186
- customize_field (field )
115
+ transform_field (field )
187
116
fields [field .name ] = field
188
-
189
117
return fields
190
118
191
119
def get_extra_fields (self , model , fields ):
192
- """
193
- Returns a dictionary of fields that will be added to the historical
194
- record model, in addition to the ones returned by copy_fields below.
195
- """
120
+ """Return dict of extra fields added to the historical record model"""
121
+
122
+ user_model = getattr (settings , 'AUTH_USER_MODEL' , 'auth.User' )
196
123
197
124
@models .permalink
198
125
def revert_url (self ):
@@ -207,8 +134,7 @@ def get_instance(self):
207
134
return {
208
135
'history_id' : models .AutoField (primary_key = True ),
209
136
'history_date' : models .DateTimeField (auto_now_add = True ),
210
- 'history_user' : models .ForeignKey (
211
- getattr (settings , 'AUTH_USER_MODEL' , 'auth.User' ), null = True ),
137
+ 'history_user' : models .ForeignKey (user_model , null = True ),
212
138
'history_type' : models .CharField (max_length = 1 , choices = (
213
139
('+' , 'Created' ),
214
140
('~' , 'Changed' ),
@@ -248,10 +174,87 @@ def create_historical_record(self, instance, type):
248
174
manager .create (history_type = type , history_user = history_user , ** attrs )
249
175
250
176
177
+ def get_custom_fk_class (parent_type ):
178
+ class CustomForeignKey (parent_type ):
179
+
180
+ def get_attname (self ):
181
+ return self .name
182
+
183
+ def do_related_class (self , other , cls ):
184
+ # this hooks into contribute_to_class() and this is
185
+ # called specifically after the class_prepared signal
186
+ to_field = copy .copy (self .rel .to ._meta .pk )
187
+ field = self
188
+ if isinstance (to_field , models .AutoField ):
189
+ field .__class__ = models .IntegerField
190
+ else :
191
+ field .__class__ = to_field .__class__
192
+ excluded_prefixes = ("_" , "__" )
193
+ excluded_attributes = (
194
+ "rel" ,
195
+ "creation_counter" ,
196
+ "validators" ,
197
+ "error_messages" ,
198
+ "attname" ,
199
+ "column" ,
200
+ "help_text" ,
201
+ "name" ,
202
+ "model" ,
203
+ "unique_for_year" ,
204
+ "unique_for_date" ,
205
+ "unique_for_month" ,
206
+ "db_tablespace" ,
207
+ "db_index" ,
208
+ "db_column" ,
209
+ "default" ,
210
+ "auto_created" ,
211
+ "null" ,
212
+ "blank" ,
213
+ )
214
+ for key , val in to_field .__dict__ .items ():
215
+ if (isinstance (key , basestring )
216
+ and not key .startswith (excluded_prefixes )
217
+ and not key in excluded_attributes ):
218
+ setattr (field , key , val )
219
+ transform_field (field )
220
+ field .rel = None
221
+
222
+ def contribute_to_class (self , cls , name ):
223
+ # HACK: remove annoying descriptor (don't super())
224
+ RelatedField .contribute_to_class (self , cls , name )
225
+
226
+ return CustomForeignKey
227
+
228
+
229
+ def transform_field (field ):
230
+ """Customize field appropriately for use in historical model"""
231
+ field .name = field .attname
232
+ if isinstance (field , models .AutoField ):
233
+ # The historical model gets its own AutoField, so any
234
+ # existing one must be replaced with an IntegerField.
235
+ field .__class__ = models .IntegerField
236
+ elif isinstance (field , models .FileField ):
237
+ # Don't copy file, just path.
238
+ field .__class__ = models .TextField
239
+
240
+ # Historical instance shouldn't change create/update timestamps
241
+ field .auto_now = False
242
+ field .auto_now_add = False
243
+
244
+ if field .primary_key or field .unique :
245
+ # Unique fields can no longer be guaranteed unique,
246
+ # but they should still be indexed for faster lookups.
247
+ field .primary_key = False
248
+ field ._unique = False
249
+ field .db_index = True
250
+ field .serialize = True
251
+
252
+
251
253
class HistoricalObjectDescriptor (object ):
252
254
def __init__ (self , model ):
253
255
self .model = model
254
256
255
257
def __get__ (self , instance , owner ):
256
- values = (getattr (instance , f .attname ) for f in self .model ._meta .fields )
258
+ values = (getattr (instance , f .attname )
259
+ for f in self .model ._meta .fields )
257
260
return self .model (* values )
0 commit comments