1 from django.db import models
2
3 from django.contrib.contenttypes.models import ContentType
4 from django.contrib.contenttypes import generic
5 from django.dispatch import dispatcher
6
7 import graph, citation, user, tag, rating
8 from user import User
9 from utils import current_user, cached_property, mixin_property, mixin_method, mixin
10
11 from lib.threadlocals_middleware import get_current_request
12
13 """
14 @summary:
15 The L{log} module includes several classes tasked with tracking use
16 of and changes to the database.
17
18 @author: Daniel Ring
19 @organization: ThoughtAndMemory.org
20 @copyright: ThoughtAndMemoyr.org
21 @contact: lucy@thoughtandmemory.org, dfring@gmail.com
22
23 @version: 0.22a
24 """
25
26
27 -class Visit(models.Model):
28 """
29 @summary:
30 A B{Visit} is a record of a single HTTP request. One is recorded
31 everytime Django serves up a page, so there will be lots of these.
32 When the site is public, we'll have to trim this down or regularly
33 flush the database, but for as long as we have the storage space,
34 this will help us evaluate traffic and analyze how users are moving
35 through the site.
36
37 @ivar path: The path (not including domain) for this page request.
38 @ivar user: The user, if logged in.
39 @ivar time: The time at the start of the request.
40 @ivar duration: The amount of time it took to process the request.
41 @ivar sql_duration: The cumulative time spent querying the database
42 during this request.
43 @ivar ip: The visitor's IP address.
44 @ivar remote_host: The HTTP_REMOTE_HOST header passed by the visitor.
45 @ivar user_agent: The HTTP_USER_AGENT header passed by the visitor.
46 This should describe their browser.
47 @ivar referer: The HTTP_REFERER header passed by the visitor. This
48 should be the page from which they linked to this one.
49 """
50 path = models.CharField(max_length=255)
51
52 user = models.ForeignKey(User)
53 time = models.DateTimeField(auto_now_add=True)
54 duration = models.FloatField()
55 sql_duration = models.FloatField()
56
57 ip = models.IPAddressField()
58 remote_host = models.CharField(max_length=255)
59 user_agent = models.CharField(max_length=255)
60 referer = models.CharField(max_length=255)
61
62 @classmethod
63 - def make(klass, path, user, time, duration, sql_duration, ip, remote_host, user_agent, referer):
73
76
77
79 """
80 @summary:
81 An B{Action} is a record of a database-changing event. All database
82 changes are logged, including creation of:
83 1. Node
84 2. UPC
85 3. Edge
86 4. Dimension
87 5. Behavior
88 6. Argument
89 7. ArgumentLink
90 8. Citation
91 9. Source
92 10. Article
93 11. Author
94 12. Tag
95 13. Tagging
96 The information stored when an action is logged includes:
97 1. User
98 2. IP
99 3. Time
100 4. Item - the affected database row
101
102 @ivar user: The User associated with this activity.
103 @type user: User
104 @ivar ip: The IP address used when the activity occurred.
105 @type ip: string
106 @ivar time: The time the activity occurred.
107 @type time: datetime
108 @ivar item: This is a polymorphic link to the affected item.
109 @type item: Any
110 @ivar item_type: This is an index into the django_content_types
111 table, which stores a row for each model in the application.
112 @type item_type: int (an id in the ContentTypes table)
113 @ivar item_id: This is the primary key of the affected item.
114 @type item_id: int
115 """
116
117 ACTING_MODELS = [
118 graph.Node, graph.NodeType, graph.NodeTypeCast,
119 graph.Edge, graph.EdgeType, graph.EdgeTypeCast,
120 graph.Argument, graph.Dimension, graph.Behavior, graph.UPC,
121 citation.Source, citation.Author, citation.Article, citation.Citation,
122 tag.Tag, tag.Tagging,
123 rating.Rating,
124 user.UserDimensionWeighting]
125
126 @classmethod
128 """
129 @summary: B{_apply} takes a list of models and to each model it:
130 1. adds B{ActionTarget} as a base class (in I{__bases__}) to
131 provide a common interface to the models;
132 2. assigns an instance of ActionTargetManager to I{actions}
133 which limits the scope to within a single model
134 3. registers listeners to call methods in L{Log} regarding events
135 on subclassed models; specifically:
136 1. L{Log.log_save} is called before a row is saved
137 2. L{Log.log_create} is called after a row is created
138 3. L{Log.log_delete} is called before a row is deleted
139 (this should not happen.)
140
141 @return: None
142 @rtype: NoneType
143 """
144 mixin(ActionMixin, Action.ACTING_MODELS)
145
146 for model_class in Action.ACTING_MODELS:
147
148 dispatcher.connect(Action.on_create, signal=models.signals.post_save, sender=model_class)
149
150
151
152
153
154 CREATE = 1
155
156
157 type = models.PositiveIntegerField(default=0)
158 label = models.CharField(max_length=255)
159 description = models.TextField()
160
161 user = models.ForeignKey(User)
162 ip = models.IPAddressField()
163 time = models.DateTimeField(auto_now_add=True)
164
165 item_type = models.ForeignKey(ContentType)
166 item_id = models.PositiveIntegerField()
167 item = generic.GenericForeignKey('item_type', 'item_id')
168
169 @classmethod
170 - def on_save(klass, signal, sender, instance):
171 """
172 @summary:
173 This method called anytime an object is about to be saved. This is where
174 we can check the I{instance.original_values} dict to determine whether
175 the object has been changed.
176
177 From here, we can raise an exception to stop the object from being saved.
178 This is currently not done, but will eventually check for the logged-in
179 status of the user.
180
181 @return: None
182 @rtype: NoneType
183 """
184
185
186 pass
187
188 @classmethod
189 - def on_create(klass, signal, sender, instance, created):
190 """
191 @summary:
192 This method is called every time an object has been saved. This is where
193 L{Action} objects are created for new objects.
194
195 @return: None
196 @rtype: NoneType
197 """
198 if created:
199 label = "Added: %s, id=%s, str=%s" % (instance.__class__.__name__, instance.id, unicode(instance))
200 a = Action.add(type=Action.CREATE, item=instance, label=label, description="Hooray!")
201
202 return
203
204 @classmethod
205 - def on_delete(klass, signal, sender, instance):
206 """
207 @summary:
208 This method is called every time an object is deleted. This should not
209 happen with the current database setup.
210
211 @return: None
212 @rtype: NoneType
213 """
214 pass
215
216 @classmethod
218 """
219 @summary: Returns a QuerySet containing all Actions for which
220 the specific User is responsible.
221 @rtype: QuerySet
222 """
223 return Action.objects.filter(user=user)
224
225 @classmethod
226 - def make(klass, item, type=1, label="<no label>", description="", ip="", user=None):
227 """
228 @summary: Instantiates, but does not save, a new Action object.
229
230 @param item: The object this action concerns.
231 @type item: Any
232 @param type: The type of Action. See the list of types in L{Action}.
233 @type type: int
234 @param label: A brief description of the activity being recorded.
235 @type label: string
236 @param description: The description of the action being created.
237 This defaults to the empty string if none is provided.
238 @type description: string
239
240 """
241 request = get_current_request()
242 if request:
243 ip = request.META.get('REMOTE_ADDR')
244 user = request.user
245 if not isinstance(user, User):
246 user = User(id=1)
247 return Action(type=1,
248 label=label,
249 description=description,
250 item_type=ContentType.objects.get_for_model(item),
251 item_id=item.id,
252 user=user,
253 ip=ip)
254
256 """
257 @summary:
258
259 @return:
260 @rtype:
261 """
262 return Action.by_action(self)
263
264 @classmethod
274
275 @classmethod
284
289
290
291 @mixin_property([user.UserExtended])
293 """
294 @summary: Returns a QuerySet containing all actions by this user,
295 limited to the specified type, if provided.
296 @rtype: QuerySet
297 """
298 return Action.by_user(self)
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
319 """
320 @summary:
321 B{ActionMixin} is a mix-in used to provide common methods, attributes,
322 and hooks to all the models whose changes are logged by L{log.Action}.
323
324 At initialization, ActionTarget.B{_apply} is applied to all the models
325 listed in L{log.Action}.
326
327 @ivar actions:
328 @type actions: ActionTargetManager
329 @ivar log:
330 @type log: LogManager
331 """
332
333 @classmethod
335 """
336 @summary: Returns a QuerySet containing the appropriate models,
337 depending which Model class this method was called on.
338 @rtype: QuerySet
339 """
340 db_table = klass._meta.db_table
341 item_type_id = ContentType.objects.get_for_model(klass).id
342 return klass.objects.extra(
343 tables=['db_action AS action'],
344 where =['%s.id = action.item_id' % db_table,
345 'action.item_type_id = %s' % item_type_id,
346 'action.user_id=%s' % user.id])
347
348
349 @cached_property
351 """
352 @summary: Returns the Action object logged at the time of this
353 object's creation.
354 @rtype: Action
355 """
356 if not hasattr(self, 'cached_action'):
357 action = Action.by_item(self).select_related()[:1]
358 if len(action) == 0:
359 raise RuntimeError("No action object for '%s'" % self)
360 self.cached_action = action[0]
361 return self.cached_action
362
363 @property
365 """
366 @summary: Returns the User responsible for creating this object.
367 @rtype: User
368 """
369 return self.action.user
370
371
372 @property
374 """
375 @summary: Returns the time at which this object was created.
376 @rtype: datetime
377 """
378 return self.action.time
379
380