├── AUTHORS
├── CHANGELOG.textile
├── MANIFEST.in
├── README.rst
├── __pkginfo__.py
├── django_redis_engine
├── __init__.py
├── base.py
├── client.py
├── compiler.py
├── creation.py
├── index_utils.py
├── redis_entity.py
├── redis_transaction.py
└── serializer.py
├── normalize_whitespace
├── setup.py
└── testproject
├── __init__.py
├── dbindexes.py
├── manage.py
├── settings.py
├── templates
├── 404.html
├── 500.html
├── aproposito.html
├── base.html
├── come_aiutare.html
├── home.html
├── login.html
├── menu.html
├── pagination.html
└── registration
│ └── login.html
├── testapp
├── __init__.py
├── dbindexes.py
├── forms.py
├── models.py
├── redis_settings.py
├── templates
│ └── testapp
│ │ ├── .html
│ │ ├── add_answer.html
│ │ ├── add_post.html
│ │ ├── forms
│ │ ├── AnswerForm.html
│ │ └── MyForm.html
│ │ └── posts.html
├── tests.py
├── urls.py
└── views.py
└── urls.py
/AUTHORS:
--------------------------------------------------------------------------------
1 | Mirko Rossini
2 |
3 |
--------------------------------------------------------------------------------
/CHANGELOG.textile:
--------------------------------------------------------------------------------
1 | 23-04-2011
2 |
3 | Added tests for (i)endswith
4 |
5 |
6 | General optimization with pipelines and persistent connections
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include MANIFEST.in
2 | include README.rst
3 | include AUTHORS
4 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ==========================================
2 | Django Redis Engine for django-nonrel 0.1
3 | ==========================================
4 |
5 | Requirements
6 | ============
7 | * Redis
8 | * Redis bindings for python
9 | * `Django-nonrel`_
10 | * `djangotoolbox`_
11 | .. _Django-nonrel: http://bitbucket.org/wkornewald/django-nonrel
12 | .. _djangotoolbox: http://bitbucket.org/wkornedwald/djangotoolbox
13 |
14 | Optional Requirements
15 | ============
16 | * dbindexer
17 |
18 | Features
19 | ========
20 | Indexing for:
21 | * startswith,istartswith
22 | * endswith,iendswith
23 | * gt,gte,lt,lte
24 | * contains (using dbindexer)
25 |
26 | Redis transaction support: you can execute multiple insert of django objects in one single pipeline. See testproject/testapp/tests.py
27 | Count queries
28 |
29 |
30 | Missing features
31 | ===========
32 | Aggregate queries
33 | Documentation (although testproject is self-documented)
34 |
35 |
36 |
37 | Contributing
38 | ============
39 | You are highly encouraged to participate in the development, simply use
40 | GitHub's fork/pull request system.
41 | If you don't like GitHub (for some reason) you're welcome
42 | to send regular patches to the mailing list.
43 |
--------------------------------------------------------------------------------
/__pkginfo__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import codecs
4 | import django_redis_engine as distmeta
5 |
6 | distname = 'django_redis_engine'
7 | numversion = distmeta.__version__
8 | version = '.'.join(map(str, numversion))
9 | license = '2-clause BSD'
10 | author = distmeta.__author__
11 | author_email = distmeta.__contact__
12 |
13 | short_desc = "A Redis django db backend."
14 | long_desc = codecs.open('README.rst', 'r', 'utf-8').read()
15 |
16 | install_requires = [ 'django>=1.2', 'djangotoolbox']
17 | pyversions = ['2', '2.4', '2.5', '2.6', '2.7']
18 | docformat = distmeta.__docformat__
19 | include_dirs = []
20 |
--------------------------------------------------------------------------------
/django_redis_engine/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | __version__ = (0, 1, 0)
5 | __author__ = "Mirko Rossini"
6 | __contact__ = "isaidyep@gmail.com"
7 | __docformat__ = "restructuredtext"
8 |
9 |
10 |
--------------------------------------------------------------------------------
/django_redis_engine/base.py:
--------------------------------------------------------------------------------
1 | from django.core.exceptions import ImproperlyConfigured
2 | from django.conf import settings
3 |
4 | import redis
5 | from .creation import DatabaseCreation
6 | from .client import DatabaseClient
7 |
8 | from djangotoolbox.db.base import (
9 | NonrelDatabaseFeatures,
10 | NonrelDatabaseWrapper,
11 | NonrelDatabaseValidation,
12 | NonrelDatabaseIntrospection,
13 | NonrelDatabaseOperations
14 | )
15 |
16 | from datetime import datetime
17 |
18 | class ImproperlyConfiguredWarning(Warning):
19 | pass
20 |
21 | class DatabaseFeatures(NonrelDatabaseFeatures):
22 | string_based_auto_field = True
23 | supports_dicts = True
24 |
25 | class DatabaseOperations(NonrelDatabaseOperations):
26 | compiler_module = __name__.rsplit('.', 1)[0] + '.compiler'
27 |
28 | def max_name_length(self):
29 | return 254
30 |
31 | def check_aggregate_support(self, aggregate):
32 | raise NotImplementedError("django-redis-engine does not support %r "
33 | "aggregates" % type(aggregate))
34 |
35 | def sql_flush(self, style, tables, sequence_list):
36 | """
37 | Returns a list of SQL statements that have to be executed to drop
38 | all `tables`. Not implemented yes, returns an empty list.
39 | """
40 |
41 | return []
42 |
43 | def value_to_db_date(self, value):
44 | if value is None:
45 | return None
46 | return datetime(value.year, value.month, value.day)
47 |
48 | def value_to_db_time(self, value):
49 | if value is None:
50 | return None
51 | return datetime(1, 1, 1, value.hour, value.minute, value.second,
52 | value.microsecond)
53 |
54 |
55 | class DatabaseValidation(NonrelDatabaseValidation):
56 | pass
57 |
58 |
59 | class DatabaseIntrospection(NonrelDatabaseIntrospection):
60 | """Database Introspection"""
61 |
62 | def table_names(self):
63 | """ Show defined models """
64 | return []#self.connection.db_connection.collection_names()
65 |
66 | def sequence_list(self):
67 | # Only required for backends that support ManyToMany relations
68 | pass
69 |
70 |
71 | class DatabaseWrapper(NonrelDatabaseWrapper):
72 | safe_inserts = False
73 | wait_for_slaves = 0
74 | _connected = False
75 |
76 | def __init__(self, *args, **kwargs):
77 | super(DatabaseWrapper, self).__init__(*args, **kwargs)
78 | self.features = DatabaseFeatures(self)
79 | self.ops = DatabaseOperations(self)
80 | self.client = DatabaseClient(self)
81 | self.creation = DatabaseCreation(self)
82 | self.validation = DatabaseValidation(self)
83 | self.introspection = DatabaseIntrospection(self)
84 |
85 | def _cursor(self):
86 |
87 | try:
88 | return self._connection
89 | except:
90 | pass
91 | self._connect()
92 | return self._connection
93 |
94 | @property
95 | def db_connection(self):
96 | """
97 | Returns the db_connection instance
98 |
99 | """
100 | try:
101 | if self._connection is not None:
102 | return self._connection
103 | except:
104 | self._connect()
105 | return self._db_connection
106 |
107 | def _connect(self):
108 | import traceback
109 | import sys
110 | #print '-------------------'
111 | #traceback.print_stack()
112 | #print '-------------------'
113 | if not self._connected:
114 | host = self.settings_dict['HOST'] or None
115 | port = self.settings_dict.get('PORT', None) or None
116 | user = self.settings_dict.get('USER', None)
117 | password = self.settings_dict.get('PASSWORD')
118 | self.db_name = self.settings_dict['NAME']
119 | try:
120 | self.exact_all = settings.REDIS_EXACT_ALL
121 | except AttributeError:
122 | self.exact_all = True
123 |
124 | self.safe_inserts = self.settings_dict.get('SAFE_INSERTS', False)
125 |
126 | self.wait_for_slaves = self.settings_dict.get('WAIT_FOR_SLAVES', 0)
127 | slave_okay = self.settings_dict.get('SLAVE_OKAY', False)
128 |
129 | try:
130 | if host is not None:
131 | assert isinstance(host, basestring), \
132 | 'If set, HOST must be a string'
133 |
134 | if port:
135 | try:
136 | port = int(port)
137 | except ValueError:
138 | raise ImproperlyConfigured(
139 | 'If set, PORT must be an integer')
140 |
141 | assert isinstance(self.safe_inserts, bool), \
142 | 'If set, SAFE_INSERTS must be True or False'
143 | assert isinstance(self.wait_for_slaves, int), \
144 | 'If set, WAIT_FOR_SLAVES must be an integer'
145 | except AssertionError, e:
146 | raise ImproperlyConfigured(e)
147 |
148 | self._connection = redis.Redis(host=host,
149 | port=port,
150 | # slave_okay=slave_okay
151 | )
152 |
153 | if user and password:
154 | auth = self._connection[self.db_name].authenticate(user,
155 | password)
156 | if not auth:
157 | raise ImproperlyConfigured("Username and/or password for "
158 | "the Redis db are not correct")
159 |
160 | self._db_connection = self._connection#[self.db_name]
161 |
162 |
163 | self._connected = True
164 |
165 | # TODO: signal! (see Alex' backend)
166 |
--------------------------------------------------------------------------------
/django_redis_engine/client.py:
--------------------------------------------------------------------------------
1 | from django.db.backends import BaseDatabaseClient
2 |
3 | class DatabaseClient(BaseDatabaseClient):
4 | executable_name = 'redis'
5 |
--------------------------------------------------------------------------------
/django_redis_engine/compiler.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import re
3 | import datetime
4 |
5 | from functools import wraps
6 |
7 | from django.db.utils import DatabaseError
8 | from django.db.models.fields import NOT_PROVIDED
9 | from django.db.models import F
10 |
11 | from django.db.models.sql import aggregates as sqlaggregates
12 | from django.db.models.sql.constants import MULTI, SINGLE
13 | from django.db.models.sql.where import AND, OR
14 | from django.utils.tree import Node
15 | from redis_entity import RedisEntity,split_db_type,hash_for_redis,get_hash_key,get_set_key,get_list_key,enpickle,unpickle
16 |
17 | from index_utils import get_indexes,create_indexes,delete_indexes,filter_with_index,isiterable
18 |
19 | import pickle
20 | import redis
21 |
22 |
23 | from djangotoolbox.db.basecompiler import NonrelQuery, NonrelCompiler, \
24 | NonrelInsertCompiler, NonrelUpdateCompiler, NonrelDeleteCompiler
25 |
26 |
27 |
28 | #TODO pipeline!!!!!!!!!!!!!!!!!!!!!
29 |
30 | def safe_regex(regex, *re_args, **re_kwargs):
31 | def wrapper(value):
32 | return re.compile(regex % re.escape(value), *re_args, **re_kwargs)
33 | wrapper.__name__ = 'safe_regex (%r)' % regex
34 | return wrapper
35 |
36 | OPERATORS_MAP = {
37 | 'exact': lambda val: val,
38 | # 'iexact': safe_regex('^%s$', re.IGNORECASE),
39 | # 'startswith': safe_regex('^%s'),
40 | # 'istartswith': safe_regex('^%s', re.IGNORECASE),
41 | # 'endswith': safe_regex('%s$'),
42 | # 'iendswith': safe_regex('%s$', re.IGNORECASE),
43 | # 'contains': safe_regex('%s'),
44 | # 'icontains': safe_regex('%s', re.IGNORECASE),
45 | # 'regex': lambda val: re.compile(val),
46 | # 'iregex': lambda val: re.compile(val, re.IGNORECASE),
47 | # 'gt': lambda val: {'$gt': val},
48 | # 'gte': lambda val: {'$gte': val},
49 | # 'lt': lambda val: {'$lt': val},
50 | # 'lte': lambda val: {'$lte': val},
51 | # 'range': lambda val: {'$gte': val[0], '$lte': val[1]},
52 | # 'year': lambda val: {'$gte': val[0], '$lt': val[1]},
53 | # 'isnull': lambda val: None if val else {'$ne': None},
54 | 'in': lambda val: {'$in': val},
55 | }
56 |
57 | NEGATED_OPERATORS_MAP = {
58 | 'exact': lambda val: {'$ne': val},
59 | 'gt': lambda val: {'$lte': val},
60 | 'gte': lambda val: {'$lt': val},
61 | 'lt': lambda val: {'$gte': val},
62 | 'lte': lambda val: {'$gt': val},
63 | 'isnull': lambda val: {'$ne': None} if val else None,
64 | 'in': lambda val: val
65 | }
66 |
67 |
68 | def first(test_func, iterable):
69 | for item in iterable:
70 | if test_func(item):
71 | return item
72 |
73 | def safe_call(func):
74 | @wraps(func)
75 | def wrapper(*args, **kwargs):
76 | try:
77 | return func(*args, **kwargs)
78 | except Exception,e:
79 | raise DatabaseError, DatabaseError(str(e)), sys.exc_info()[2]
80 | return wrapper
81 |
82 |
83 | class DBQuery(NonrelQuery):
84 | # ----------------------------------------------
85 | # Public API
86 | # ----------------------------------------------
87 | def __init__(self, compiler, fields):
88 | super(DBQuery, self).__init__(compiler, fields)
89 | #print fields
90 | #print dir(self.query.get_meta())
91 | self.db_table = self.query.get_meta().db_table
92 | self.indexes = get_indexes()
93 | self.indexes_for_model = self.indexes.get(self.query.model,{})
94 | self._collection = self.connection.db_connection
95 | self.db_name = self.connection.db_name
96 | #self.connection.exact_all
97 | self._ordering = []
98 | self.db_query = {}
99 |
100 | # This is needed for debugging
101 | def __repr__(self):
102 | return '' % (self.db_query, self._ordering)
103 |
104 | @property
105 | def collection(self):
106 | return self._collection
107 |
108 | def fetch(self, low_mark, high_mark):
109 |
110 | results = self._get_results()
111 | #print 'here results ',results
112 | primarykey_column = self.query.get_meta().pk.column
113 | for e_id in results:
114 | yield RedisEntity(e_id,self._collection,self.db_table,primarykey_column,self.query.get_meta(),self.db_name)
115 |
116 | @safe_call
117 | def count(self, limit=None): #TODO is this right?
118 | results = self._get_results()
119 | if limit is not None:
120 | results = results[:limit]
121 | return len(results)
122 |
123 | @safe_call
124 | def delete(self):
125 |
126 | db_table = self.query.get_meta().db_table
127 | results = self._get_results()
128 |
129 | pipeline = self._collection.pipeline(transaction = False)
130 | for res in results:
131 | pipeline.hgetall(get_hash_key(self.db_name,db_table,res))
132 | hmaps_ret = pipeline.execute()
133 | hmaps = ((results[n],hmaps_ret[n]) for n in range(len(hmaps_ret)))
134 |
135 | pipeline = self._collection.pipeline(transaction = False)
136 | for res,hmap in hmaps:
137 | pipeline.delete(get_hash_key(self.db_name,db_table,res))
138 | for field,val in hmap.iteritems():
139 | val = unpickle(val)
140 | if val is not None:
141 | #INDEXES
142 | if field in self.indexes_for_model or self.connection.exact_all:
143 | try:
144 | indexes_for_field = self.indexes_for_model[field]
145 | except KeyError:
146 | indexes_for_field = ()
147 | if 'exact' not in indexes_for_field and self.connection.exact_all:
148 | indexes_for_field += 'exact',
149 | delete_indexes( field,
150 | val,
151 | indexes_for_field,
152 | pipeline,
153 | get_hash_key(self.db_name,db_table,res),
154 | db_table,
155 | res,
156 | self.db_name,
157 | )
158 | pipeline.srem(self.db_name+'_'+db_table+'_ids' ,res)
159 | pipeline.execute()
160 |
161 |
162 | @safe_call
163 | def order_by(self, ordering):
164 | if len(ordering) > 1:
165 | raise DatabaseError('Only one order is allowed')
166 | for order in ordering:
167 | if order.startswith('-'):
168 | order, direction = order[1:], 'desc'
169 | else:
170 | direction = 'asc'
171 | if order == self.query.get_meta().pk.column:
172 | order = '_id'
173 | else:
174 | pass #raise DatabaseError('You can only order by PK') TODO check when order index support is active
175 | self._ordering.append((order, direction))
176 | return self
177 |
178 | @safe_call
179 | def add_filter(self, column, lookup_type, negated, db_type, value):
180 | """add filter
181 | used by default add_filters implementation
182 |
183 | """
184 | #print "ADD FILTER -- ",column, lookup_type, negated, db_type, value
185 | if column == self.query.get_meta().pk.column:
186 | if lookup_type in ('exact','in'):
187 | #print "cisiamo"
188 | #print "db_query?"
189 | #print self.db_query
190 | try:
191 | self.db_query[column][lookup_type]
192 | raise DatabaseError("You can't apply multiple AND filters " #Double filter on pk
193 | "on the primary key. "
194 | "Did you mean __in=[...]?")
195 |
196 | except KeyError:
197 | self.db_query.update({column:{lookup_type:value}})
198 |
199 | else:
200 | if lookup_type in ('exact','in'):
201 | if not self.connection.exact_all and 'exact' not in self.indexes_for_model.get(column,()):
202 | raise DatabaseError('Lookup %s on column %s is not allowed (have you tried redis_indexes? )' % (lookup_type,column))
203 | else:self.db_query.update({column:{lookup_type:value}})
204 | else:
205 | if lookup_type in self.indexes_for_model.get(column,()):
206 | self.db_query.update({column:{lookup_type:value}})
207 |
208 | else:
209 | raise DatabaseError('Lookup %s on column %s is not allowed (have you tried redis_indexes? )' % (lookup_type,column))
210 |
211 |
212 | def _get_results(self):
213 | """
214 | see self.db_query, lookup parameters format: {'column': {lookup:value}}
215 |
216 | """
217 | pk_column = self.query.get_meta().pk.column
218 | db_table = self.query.get_meta().db_table
219 |
220 | results = self._collection.smembers(self.db_name+'_'+db_table+'_ids')
221 |
222 |
223 | for column,filteradd in self.db_query.iteritems():
224 | lookup,value = filteradd.popitem()#TODO tuple better?
225 |
226 | if pk_column == column:
227 | if lookup == 'in': #TODO meglio??
228 | results = results & set(value) #IN filter
229 | elif lookup == 'exact':
230 | results = results & set([value,])
231 |
232 | else:
233 | if lookup == 'exact':
234 | results = results & self._collection.smembers(get_set_key(self.db_name,db_table,column,value))
235 | elif lookup == 'in': #ListField or empty
236 | tempset = set()
237 | for v in value:
238 | tempset = tempset.union(self._collection.smembers(get_set_key(self.db_name,db_table,column,v) ) )
239 | results = results & tempset
240 | else:
241 | tempset = filter_with_index(lookup,value,self._collection,db_table,column,self.db_name)
242 | if tempset is not None:
243 | results = results & tempset
244 | else:
245 | results = set()
246 |
247 |
248 | if self._ordering:
249 | if self._ordering[0][1] == 'desc':
250 | results.reverse()
251 |
252 | if self.query.low_mark > 0 and self.query.high_mark is not None:
253 | results = list(results)[self.query.low_mark:self.query.high_mark]
254 | elif self.query.low_mark > 0:
255 |
256 | results = list(results)[self.query.low_mark:]
257 |
258 | elif self.query.high_mark is not None:
259 | results = list(results)[:self.query.high_mark]
260 |
261 | return list(results)
262 |
263 | class SQLCompiler(NonrelCompiler):
264 | """
265 | A simple query: no joins, no distinct, etc.
266 | """
267 | query_class = DBQuery
268 |
269 | def _split_db_type(self, db_type):
270 | try:
271 | db_type, db_subtype = db_type.split(':', 1)
272 | except ValueError:
273 | db_subtype = None
274 | return db_type, db_subtype
275 |
276 | @safe_call # see #7
277 | def convert_value_for_db(self, db_type, value):
278 | #print db_type,' ',value
279 | if db_type is None or value is None:
280 | return value
281 |
282 | db_type, db_subtype = self._split_db_type(db_type)
283 | if db_subtype is not None:
284 | if isinstance(value, (set, list, tuple)):
285 |
286 | return [self.convert_value_for_db(db_subtype, subvalue)
287 | for subvalue in value]
288 | elif isinstance(value, dict):
289 | return dict((key, self.convert_value_for_db(db_subtype, subvalue))
290 | for key, subvalue in value.iteritems())
291 |
292 | if isinstance(value, (set, list, tuple)):
293 | # most likely a list of ObjectIds when doing a .delete() query
294 | return [self.convert_value_for_db(db_type, val) for val in value]
295 |
296 | if db_type == 'objectid':
297 | return value
298 | return value
299 |
300 | @safe_call # see #7
301 | def convert_value_from_db(self, db_type, value):
302 | if db_type is None:
303 | return value
304 |
305 | if value is None or value is NOT_PROVIDED:
306 | # ^^^ it is *crucial* that this is not written as 'in (None, NOT_PROVIDED)'
307 | # because that would call value's __eq__ method, which in case value
308 | # is an instance of serializer.LazyModelInstance does a database query.
309 | return None
310 |
311 | db_type, db_subtype = self._split_db_type(db_type)
312 | if db_subtype is not None:
313 | for field, type_ in [('SetField', set), ('ListField', list)]:
314 | if db_type == field:
315 | return type_(self.convert_value_from_db(db_subtype, subvalue)
316 | for subvalue in value)
317 | if db_type == 'DictField':
318 | return dict((key, self.convert_value_from_db(db_subtype, subvalue))
319 | for key, subvalue in value.iteritems())
320 |
321 | if db_type == 'objectid':
322 | return unicode(value)
323 |
324 | if db_type == 'date':
325 | return datetime.date(value.year, value.month, value.day)
326 |
327 | if db_type == 'time':
328 | return datetime.time(value.hour, value.minute, value.second,
329 | value.microsecond)
330 | return value
331 |
332 | def insert_params(self):
333 | conn = self.connection
334 | params = {'safe': conn.safe_inserts}
335 | if conn.wait_for_slaves:
336 | params['w'] = conn.wait_for_slaves
337 | return params
338 |
339 | @property
340 | def _collection(self):
341 | #TODO multi db
342 | return self.connection.db_connection
343 | @property
344 | def db_name(self):
345 | return self.connection.db_name
346 |
347 | def _save(self, data, return_id=False):
348 |
349 | db_table = self.query.get_meta().db_table
350 | indexes = get_indexes()
351 | indexes_for_model = indexes.get(self.query.model,{})
352 |
353 | pipeline = self._collection.pipeline(transaction = False)
354 |
355 | h_map = {}
356 | h_map_old = {}
357 |
358 | if '_id' in data:
359 | pk = data['_id']
360 | new = False
361 | h_map_old = self._collection.hgetall(get_hash_key(self.db_name,db_table,pk))
362 | else:
363 | pk = self._collection.incr(self.db_name+'_'+db_table+"_id")
364 | new = True
365 |
366 | for key,value in data.iteritems():
367 |
368 | if new:
369 | old = None
370 | h_map[key] = pickle.dumps(value)
371 | else:
372 | if key == "_id": continue
373 | old = pickle.loads(h_map_old[key])
374 |
375 | if old != value:
376 | h_map[key] = pickle.dumps(value)
377 |
378 | if key in indexes_for_model or self.connection.exact_all:
379 | try:
380 | indexes_for_field = indexes_for_model[key]
381 | except KeyError:
382 | indexes_for_field = ()
383 | if 'exact' not in indexes_for_field and self.connection.exact_all:
384 | indexes_for_field += 'exact',
385 | create_indexes( key,
386 | value,
387 | old,
388 | indexes_for_field,
389 | pipeline,
390 | db_table+'_'+str(pk),
391 | db_table,
392 | pk,
393 | self.db_name,
394 | )
395 |
396 | if '_id' not in data: pipeline.sadd(self.db_name+'_'+db_table+"_ids" ,pk)
397 |
398 | pipeline.hmset(get_hash_key(self.db_name,db_table,pk),h_map)
399 | pipeline.execute()
400 | if return_id:
401 | return unicode(pk)
402 |
403 | def execute_sql(self, result_type=MULTI):
404 | """
405 | Handles aggregate/count queries
406 | """
407 |
408 |
409 | raise NotImplementedError('Not implemented')
410 |
411 |
412 | class SQLInsertCompiler(NonrelInsertCompiler, SQLCompiler):
413 | @safe_call
414 | def insert(self, data, return_id=False):
415 | pk_column = self.query.get_meta().pk.column
416 | try:
417 | data['_id'] = data.pop(pk_column)
418 | except KeyError:
419 | pass
420 | return self._save(data, return_id)
421 |
422 | class SQLUpdateCompiler(NonrelUpdateCompiler, SQLCompiler):
423 | pass
424 | class SQLDeleteCompiler(NonrelDeleteCompiler, SQLCompiler):
425 | pass
426 |
--------------------------------------------------------------------------------
/django_redis_engine/creation.py:
--------------------------------------------------------------------------------
1 | from djangotoolbox.db.base import NonrelDatabaseCreation
2 |
3 | TEST_DATABASE_PREFIX = 'test_'
4 |
5 | class DatabaseCreation(NonrelDatabaseCreation):
6 | """Database Creation class.
7 | """
8 | data_types = {
9 | 'DateTimeField': 'datetime',
10 | 'DateField': 'date',
11 | 'TimeField': 'time',
12 | 'FloatField': 'float',
13 | 'EmailField': 'unicode',
14 | 'URLField': 'unicode',
15 | 'BooleanField': 'bool',
16 | 'NullBooleanField': 'bool',
17 | 'CharField': 'unicode',
18 | 'CommaSeparatedIntegerField': 'unicode',
19 | 'IPAddressField': 'unicode',
20 | 'SlugField': 'unicode',
21 | 'FileField': 'unicode',
22 | 'FilePathField': 'unicode',
23 | 'TextField': 'unicode',
24 | 'XMLField': 'unicode',
25 | 'IntegerField': 'int',
26 | 'SmallIntegerField': 'int',
27 | 'PositiveIntegerField': 'int',
28 | 'PositiveSmallIntegerField': 'int',
29 | 'BigIntegerField': 'int',
30 | 'GenericAutoField': 'objectid',
31 | 'StringForeignKey': 'objectid',
32 | 'AutoField': 'objectid',
33 | 'RelatedAutoField': 'objectid',
34 | 'OneToOneField': 'int',
35 | 'DecimalField': 'float',
36 | }
37 |
38 | def sql_indexes_for_field(self, model, field, **kwargs):
39 | """Create Indexes for field in model.
40 |
41 | Indexes are created rundime, no need for this.
42 | Returns an empty List. (Django Compatibility)
43 | """
44 |
45 | """if field.db_index:
46 | kwargs = {}
47 | opts = model._meta
48 | col = getattr(self.connection.db_connection, opts.db_table)
49 | descending = getattr(opts, "descending_indexes", [])
50 | direction = (field.attname in descending and -1) or 1
51 | kwargs["unique"] = field.unique
52 | col.ensure_index([(field.name, direction)], **kwargs)"""
53 | return []
54 |
55 | def index_fields_group(self, model, group, **kwargs):
56 | """Create indexes for fields in group that belong to model.
57 | TODO: Necessary?
58 | """
59 | """if not isinstance(group, dict):
60 | raise TypeError("Indexes group has to be instance of dict")
61 |
62 | fields = group.pop("fields")
63 |
64 | if not isinstance(fields, (list, tuple)):
65 | raise TypeError("index_together fields has to be instance of list")
66 |
67 | opts = model._meta
68 | col = getattr(self.connection.db_connection, opts.db_table)
69 | checked_fields = []
70 | model_fields = [ f.name for f in opts.local_fields]
71 |
72 | for field in fields:
73 | field_name = field
74 | direction = 1
75 | if isinstance(field, (tuple, list)):
76 | field_name = field[0]
77 | direction = (field[1] and 1) or -1
78 | if not field_name in model_fields:
79 | from django.db.models.fields import FieldDoesNotExist
80 | raise FieldDoesNotExist('%s has no field named %r' % (opts.object_name, field_name))
81 | checked_fields.append((field_name, direction))
82 | col.ensure_index(checked_fields, **group)"""
83 |
84 | def sql_indexes_for_model(self, model, *args, **kwargs):
85 | """Creates ``model`` indexes.
86 |
87 | Probably not necessary
88 | TODO Erase?
89 | """
90 | """if not model._meta.managed or model._meta.proxy:
91 | return []
92 | fields = [f for f in model._meta.local_fields if f.db_index]
93 | if not fields and not hasattr(model._meta, "index_together") and not hasattr(model._meta, "unique_together"):
94 | return []
95 | print "Installing index for %s.%s model" % (model._meta.app_label, model._meta.object_name)
96 | for field in fields:
97 | self.sql_indexes_for_field(model, field)
98 | for group in getattr(model._meta, "index_together", []):
99 | self.index_fields_group(model, group)
100 |
101 | #unique_together support
102 | unique_together = getattr(model._meta, "unique_together", [])
103 | # Django should do this, I just wanted to be REALLY sure.
104 | if len(unique_together) > 0 and isinstance(unique_together[0], basestring):
105 | unique_together = (unique_together,)
106 | for fields in unique_together:
107 | group = { "fields" : fields, "unique" : True}
108 | self.index_fields_group(model, group)"""
109 | return []
110 |
111 | def sql_create_model(self, model, *args, **kwargs):
112 | """TODO delete...
113 | """
114 | return [], {}
115 |
116 |
117 | def set_autocommit(self):
118 | "Make sure a connection is in autocommit mode."
119 | pass
120 |
121 | def create_test_db(self, verbosity=1, autoclobber=False):
122 | if self.connection.settings_dict.get('TEST_NAME'):
123 | test_database_name = self.connection.settings_dict['TEST_NAME']
124 | elif 'NAME' in self.connection.settings_dict:
125 | test_database_name = TEST_DATABASE_PREFIX + self.connection.settings_dict['NAME']
126 | elif 'DATABASE_NAME' in self.connection.settings_dict:
127 | if self.connection.settings_dict['DATABASE_NAME'].startswith(TEST_DATABASE_PREFIX):
128 | test_database_name = self.connection.settings_dict['DATABASE_NAME']
129 | else:
130 | test_database_name = TEST_DATABASE_PREFIX + \
131 | self.connection.settings_dict['DATABASE_NAME']
132 | else:
133 | raise ValueError("Name for test database not defined")
134 |
135 | self.connection.settings_dict['NAME'] = test_database_name
136 | # This is important. Here we change the settings so that all other code
137 | # thinks that the chosen database is now the test database. This means
138 | # that nothing needs to change in the test code for working with
139 | # connections, databases and collections. It will appear the same as
140 | # when working with non-test code.
141 |
142 | # In this phase it will only drop the database if it already existed
143 | # which could potentially happen if the test database was created but
144 | # was never dropped at the end of the tests
145 | self._drop_database(test_database_name)
146 |
147 | return test_database_name
148 |
149 | def destroy_test_db(self, old_database_name, verbosity=1):
150 | """
151 | Destroy a test database, prompting the user for confirmation if the
152 | database already exists. Returns the name of the test database created.
153 | """
154 | if verbosity >= 1:
155 | print "Destroying test database '%s'..." % self.connection.alias
156 | test_database_name = self.connection.settings_dict['NAME']
157 | self._drop_database(test_database_name)
158 | self.connection.settings_dict['NAME'] = old_database_name
159 |
160 | def _drop_database(self, database_name):
161 | """Drops the database with name database_name
162 |
163 | """
164 | for k in self.connection._cursor().keys(database_name+'*'):
165 | del self.connection._cursor()[k]
166 |
--------------------------------------------------------------------------------
/django_redis_engine/index_utils.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.utils.importlib import import_module
3 | from md5 import md5
4 | from redis_entity import *
5 | _MODULE_NAMES = getattr(settings, 'REDIS_SETTINGS_MODULES', ())
6 | from redis.exceptions import WatchError
7 |
8 | #TODO update might overwrite field indexes
9 |
10 | INDEX_KEY_INFIX = "redis_index"
11 |
12 | SWITH_INDEX_SEPARATOR = '\x00'
13 |
14 | isiterable = lambda obj: getattr(obj, '__iter__', False)
15 |
16 | import datetime
17 |
18 | def val_for_insert(d):
19 | if isinstance(d,unicode):
20 | d = d
21 | elif isinstance(d,basestring) : d = unicode(d.decode('utf-8'))
22 | else: d = unicode(d)
23 | return d
24 |
25 |
26 | def get_indexes():
27 | indexes = {}
28 | for name in _MODULE_NAMES:
29 | try:
30 | indexes.update(import_module(name).INDEXES)
31 | except (ImportError, AttributeError):
32 | pass
33 |
34 | return indexes
35 |
36 | def prepare_value_for_index(index,val):
37 | if index == 'startswith': return val
38 | if index == 'istartswith': return val.lower()
39 | if index == 'endswith': return val[::-1]
40 | if index == 'iendswith': return val.lower()[::-1]
41 | if index in ('gt','gte','lt','lte'):
42 | if isinstance(val,datetime.datetime):
43 | return "%04d%02d%02d%02d%02d" % (val.year,val.month,val.day,val.hour,val.minute)
44 | if isinstance(val,datetime.time):
45 | return "%02d%02d" % (val.hour,val.minute)
46 | if isinstance(val,datetime.date):
47 | return "%04d%02d%02d" % (val.year,val.month,val.day)
48 | if isinstance(val,int):
49 | return "%20d" % val
50 | return val
51 |
52 |
53 |
54 | def create_indexes(column,data,old,indexes,conn,hash_record,table,pk,db_name):
55 | for index in indexes:
56 | if index in ('startswith','istartswith','endswith','iendswith','gt','gte','lt','lte'):
57 | if old is not None:
58 | if not isiterable(old):
59 | old = (old,)
60 | for d in old:
61 | d = prepare_value_for_index(index,d)
62 | conn.zrem(get_zset_index_key(db_name,table,INDEX_KEY_INFIX,column,index),
63 | d+SWITH_INDEX_SEPARATOR+str(pk))
64 | if not isiterable(data):
65 | data = (data,)
66 | for d in data:
67 | d = val_for_insert(d)
68 | d = prepare_value_for_index(index,d)
69 | conn.zadd(get_zset_index_key(db_name,table,INDEX_KEY_INFIX,column,index),
70 | d+SWITH_INDEX_SEPARATOR+str(pk),0)
71 | if index == 'exact':
72 | if old is not None:
73 | if not isiterable(old):
74 | old = (old,)
75 | for d in old:
76 | conn.srem(get_set_key(db_name,table,column,d),str(pk))
77 | if not isiterable(data):
78 | data = (data,)
79 | for d in data:
80 | d = val_for_insert(d)
81 | conn.sadd(get_set_key(db_name,table,column,d),pk)
82 |
83 |
84 | def delete_indexes(column,data,indexes,conn,hash_record,table,pk,db_name):
85 | for index in indexes:
86 | if index in ('startswith','istartswith','endswith','iendswith','gt','gte','lt','lte'):
87 | if not isiterable(data):
88 | data = (data,)
89 | for d in data:
90 | d = val_for_insert(d)
91 | d = prepare_value_for_index(index,d)
92 | conn.zrem(get_zset_index_key(db_name,table,INDEX_KEY_INFIX,column,index),
93 | d+SWITH_INDEX_SEPARATOR+str(pk))
94 | if index == 'exact':
95 | if not isiterable(data):
96 | data = (data,)
97 | for d in data:
98 | d = val_for_insert(d)
99 | conn.srem(get_set_key(db_name,table,column,d),str(pk))
100 |
101 | def filter_with_index(lookup,value,conn,table,column,db_name):
102 | if lookup in ('startswith','istartswith','endswith','iendswith',):
103 | value = val_for_insert(value)
104 | v = prepare_value_for_index(lookup,value)
105 |
106 | #v2 = v[:-1]+chr(ord(v[-1])+1) #last letter=next(last letter)
107 | key = get_zset_index_key(db_name,table,INDEX_KEY_INFIX,column,lookup)
108 |
109 | pipeline = conn.pipeline()
110 | conn.zadd(key,v,0)
111 | #pipeline.zadd(key,v2,0)
112 | #pipeline.execute()
113 | while True:
114 | try:
115 | conn.watch(key)
116 | up = conn.zrank(key,v)
117 | #down = conn.zrank(key,v2)
118 |
119 | pipeline.zrange(key,up+1,-1)#down-1)
120 | pipeline.zrem(key,v)
121 | #pipeline.zrem(key,v2)
122 |
123 | l = pipeline.execute()
124 | #print l
125 | r = l[0]
126 | #print l
127 | #print 'erre: ',r
128 | #print 'second pipeline',pipeline.execute()
129 | ret = set()
130 | for i in r:
131 | i = unicode(i.decode('utf8'))
132 | if i.startswith(v):
133 | splitted_string = i.split(SWITH_INDEX_SEPARATOR)
134 | if len(splitted_string) > 1:
135 | ret.add(splitted_string[-1])
136 | else:
137 | break
138 | return ret
139 | except WatchError:
140 | pass
141 | # print pipeline.execute()
142 |
143 | elif lookup in ('gt','gte','lt','lte'):
144 | value = val_for_insert(value)
145 | v = prepare_value_for_index(lookup,value)
146 | key = get_zset_index_key(db_name,table,INDEX_KEY_INFIX,column,lookup)
147 | pipeline = conn.pipeline()
148 | conn.zadd(key,v,0)
149 | while True:
150 | try:
151 | conn.watch(key)
152 | up = conn.zrank(key,v)
153 | if lookup in ('lt','lte'):
154 | pipeline.zrange(key,0,up+1)
155 | else:
156 | pipeline.zrange(key,up+1,-1)
157 | pipeline.zrem(key,v)
158 | l = pipeline.execute()
159 | r = l[0]
160 | ret = set()
161 | for i in r:
162 | i = unicode(i.decode('utf8'))
163 | splitted_string = i.split(SWITH_INDEX_SEPARATOR)
164 | if len(splitted_string) > 0 and\
165 | (lookup in ('gte','lte') or\
166 | "".join(splitted_string[:-1]) != value):
167 | ret.add(splitted_string[-1])
168 | return ret
169 | except WatchError:
170 | pass
171 | else:
172 | raise Exception('Lookup type not supported') #TODO check at index creation?
173 |
174 |
175 |
176 | #
177 |
--------------------------------------------------------------------------------
/django_redis_engine/redis_entity.py:
--------------------------------------------------------------------------------
1 | from django.db.models.fields import FieldDoesNotExist
2 | from md5 import md5
3 | import pickle
4 |
5 | class RedisEntity(object):
6 | def __init__(self,e_id,connection,db_table, pkcolumn, querymeta, db_name,empty):
7 | self.id = e_id
8 | self.connection = connection
9 | self.db_table = db_table
10 | self.pkcolumn = pkcolumn
11 | self.querymeta = querymeta
12 | self.db_name = db_name
13 | self.empty = empty
14 | if not empty:
15 | self.data = self.connection.hgetall(get_hash_key(self.db_name,self.db_table,self.id))
16 |
17 |
18 | def get(self,what,value):
19 |
20 | if self.empty: return ''
21 | if what in self.data:
22 | #print self.data,self.data[what]
23 | return unpickle(self.data[what])
24 | if what == self.pkcolumn:
25 | return self.id
26 | else:
27 | return unpickle(self.connection.hget(get_hash_key(self.db_name,self.db_table,self.id), what))
28 |
29 |
30 | def split_db_type(db_type):
31 | #TODO move somewhere else
32 | try:
33 | db_type, db_subtype = db_type.split(':', 1)
34 | except ValueError:
35 | db_subtype = None
36 | return db_type, db_subtype
37 |
38 | def get_hash_key(db_name,db_table,pk):
39 | return db_name+'_'+db_table+'_'+str(pk)
40 |
41 | def get_zset_index_key(db_name,db_table,infix,column,index):
42 | return db_name+'_'+db_table +'_' + infix + '_' + column + '_'+index
43 |
44 | def get_list_key(db_name,db_table,key,pk):
45 | return db_name+'_'+db_table+'_'+key+'_'+str(pk)
46 |
47 |
48 | def get_set_key(db_name,db_table,key,value):
49 | return db_name+'_'+db_table+'_'+key+'_'+hash_for_redis(value)
50 |
51 | def unpickle(val):
52 | if val is None:
53 | return None
54 | else:
55 | return pickle.loads(val)
56 |
57 | def enpickle(val):
58 | if val is None:
59 | return None
60 | else:
61 | return pickle.dumps(val)
62 |
63 |
64 | def hash_for_redis(val):
65 | if isinstance(val,unicode):
66 | return md5(val.encode('utf-8')).hexdigest()
67 | return md5(str(val)).hexdigest()
68 |
--------------------------------------------------------------------------------
/django_redis_engine/redis_transaction.py:
--------------------------------------------------------------------------------
1 | """
2 | This module implements a transaction manager that can be used to define
3 | transaction handling in a request or view function. It is used by transaction
4 | control middleware and decorators.
5 |
6 | The transaction manager can be in managed or in auto state. Auto state means the
7 | system is using a commit-on-save strategy (actually it's more like
8 | commit-on-change). As soon as the .save() or .delete() (or related) methods are
9 | called, a commit is made.
10 |
11 | Managed transactions don't do those commits, but will need some kind of manual
12 | or implicit commits or rollbacks.
13 | """
14 | import sys
15 |
16 | try:
17 | from functools import wraps
18 | except ImportError:
19 | from django.utils.functional import wraps # Python 2.4 fallback.
20 |
21 | from django.conf import settings
22 | from django.db import connections, DEFAULT_DB_ALIAS
23 |
24 |
25 | class RedisTransactionManagementError(Exception):
26 | """
27 | This exception is thrown when something bad happens with transaction
28 | management.
29 | """
30 | pass
31 |
32 | def redis_enter_transaction_management(managed=True, using=None):
33 | """
34 | Enters transaction management for a running thread. It must be balanced with
35 | the appropriate redis_leave_transaction_management call, since the actual state is
36 | managed as a stack.
37 |
38 | The state and dirty flag are carried over from the surrounding block or
39 | from the settings, if there is no surrounding block (dirty is always false
40 | when no current block is running).
41 | """
42 | if using is None:
43 | using = DEFAULT_DB_ALIAS
44 | connection = connections[using]
45 | connection.redis_enter_transaction_management(managed)
46 |
47 | def redis_leave_transaction_management(using=None):
48 | """
49 | Leaves transaction management for a running thread. A dirty flag is carried
50 | over to the surrounding block, as a commit will commit all changes, even
51 | those from outside. (Commits are on connection level.)
52 | """
53 | if using is None:
54 | using = DEFAULT_DB_ALIAS
55 | connection = connections[using]
56 | connection.redis_leave_transaction_management(using)
57 |
58 | def is_dirty(using=None):
59 | """
60 | Returns True if the current transaction requires a commit for changes to
61 | happen.
62 | """
63 | if using is None:
64 | using = DEFAULT_DB_ALIAS
65 | connection = connections[using]
66 | return connection.is_dirty()
67 |
68 | def set_dirty(using=None):
69 | """
70 | Sets a dirty flag for the current thread and code streak. This can be used
71 | to decide in a managed block of code to decide whether there are open
72 | changes waiting for commit.
73 | """
74 | if using is None:
75 | using = DEFAULT_DB_ALIAS
76 | connection = connections[using]
77 | connection.set_dirty()
78 |
79 | def set_clean(using=None):
80 | """
81 | Resets a dirty flag for the current thread and code streak. This can be used
82 | to decide in a managed block of code to decide whether a commit or rollback
83 | should happen.
84 | """
85 | if using is None:
86 | using = DEFAULT_DB_ALIAS
87 | connection = connections[using]
88 | connection.set_clean()
89 |
90 | def clean_savepoints(using=None):
91 | if using is None:
92 | using = DEFAULT_DB_ALIAS
93 | connection = connections[using]
94 | connection.clean_savepoints()
95 |
96 | def is_managed(using=None):
97 | """
98 | Checks whether the transaction manager is in manual or in auto state.
99 | """
100 | if using is None:
101 | using = DEFAULT_DB_ALIAS
102 | connection = connections[using]
103 | return connection.is_managed()
104 |
105 | def managed(flag=True, using=None):
106 | """
107 | Puts the transaction manager into a manual state: managed transactions have
108 | to be committed explicitly by the user. If you switch off transaction
109 | management and there is a pending commit/rollback, the data will be
110 | commited.
111 | """
112 | if using is None:
113 | using = DEFAULT_DB_ALIAS
114 | connection = connections[using]
115 | connection.redis_managed(flag)
116 |
117 | def commit_unless_managed(using=None):
118 | """
119 | Commits changes if the system is not in managed transaction mode.
120 | """
121 | if using is None:
122 | using = DEFAULT_DB_ALIAS
123 | connection = connections[using]
124 | connection.commit_unless_managed()
125 |
126 | def rollback_unless_managed(using=None):
127 | """
128 | Rolls back changes if the system is not in managed transaction mode.
129 | """
130 | if using is None:
131 | using = DEFAULT_DB_ALIAS
132 | connection = connections[using]
133 | connection.rollback_unless_managed()
134 |
135 | def commit(using=None):
136 | """
137 | Does the commit itself and resets the dirty flag.
138 | """
139 | if using is None:
140 | using = DEFAULT_DB_ALIAS
141 | connection = connections[using]
142 | connection.redis_commit()
143 |
144 | def rollback(using=None):
145 | """
146 | This function does the rollback itself and resets the dirty flag.
147 | """
148 | if using is None:
149 | using = DEFAULT_DB_ALIAS
150 | connection = connections[using]
151 | connection.rollback()
152 |
153 | def savepoint(using=None):
154 | """
155 | Creates a savepoint (if supported and required by the backend) inside the
156 | current transaction. Returns an identifier for the savepoint that will be
157 | used for the subsequent rollback or commit.
158 | """
159 | if using is None:
160 | using = DEFAULT_DB_ALIAS
161 | connection = connections[using]
162 | return connection.savepoint()
163 |
164 | def savepoint_rollback(sid, using=None):
165 | """
166 | Rolls back the most recent savepoint (if one exists). Does nothing if
167 | savepoints are not supported.
168 | """
169 | if using is None:
170 | using = DEFAULT_DB_ALIAS
171 | connection = connections[using]
172 | connection.savepoint_rollback(sid)
173 |
174 | def savepoint_redis_commit(sid, using=None):
175 | """
176 | Commits the most recent savepoint (if one exists). Does nothing if
177 | savepoints are not supported.
178 | """
179 | if using is None:
180 | using = DEFAULT_DB_ALIAS
181 | connection = connections[using]
182 | connection.savepoint_redis_commit(sid)
183 |
184 | ##############
185 | # DECORATORS #
186 | ##############
187 |
188 | class RedisTransaction(object):
189 | """
190 | Acts as either a decorator, or a context manager. If it's a decorator it
191 | takes a function and returns a wrapped function. If it's a contextmanager
192 | it's used with the ``with`` statement. In either event entering/exiting
193 | are called before and after, respectively, the function/block is executed.
194 |
195 | autocommit, commit_on_success, and commit_manually contain the
196 | implementations of entering and exiting.
197 | """
198 | def __init__(self, entering, exiting, using):
199 | self.entering = entering
200 | self.exiting = exiting
201 | self.using = using
202 |
203 | def __enter__(self):
204 | self.entering(self.using)
205 |
206 | def __exit__(self, exc_type, exc_value, traceback):
207 | self.exiting(exc_value, self.using)
208 |
209 | def __call__(self, func):
210 | @wraps(func)
211 | def inner(*args, **kwargs):
212 | # Once we drop support for Python 2.4 this block should become:
213 | # with self:
214 | # func(*args, **kwargs)
215 | self.__enter__()
216 | try:
217 | res = func(*args, **kwargs)
218 | except:
219 | self.__exit__(*sys.exc_info())
220 | raise
221 | else:
222 | self.__exit__(None, None, None)
223 | return res
224 | return inner
225 |
226 | def _transaction_func(entering, exiting, using):
227 | """
228 | Takes 3 things, an entering function (what to do to start this block of
229 | transaction management), an exiting function (what to do to end it, on both
230 | success and failure, and using which can be: None, indiciating using is
231 | DEFAULT_DB_ALIAS, a callable, indicating that using is DEFAULT_DB_ALIAS and
232 | to return the function already wrapped.
233 |
234 | Returns either a RedisTransaction objects, which is both a decorator and a
235 | context manager, or a wrapped function, if using is a callable.
236 | """
237 | # Note that although the first argument is *called* `using`, it
238 | # may actually be a function; @autocommit and @autoredis_commit('foo')
239 | # are both allowed forms.
240 | if using is None:
241 | using = DEFAULT_DB_ALIAS
242 | if callable(using):
243 | return RedisTransaction(entering, exiting, DEFAULT_DB_ALIAS)(using)
244 | return RedisTransaction(entering, exiting, using)
245 |
246 |
247 | def autoredis_commit(using=None):
248 | """
249 | Decorator that activates commit on save. This is Django's default behavior;
250 | this decorator is useful if you globally activated transaction management in
251 | your settings file and want the default behavior in some view functions.
252 | """
253 | def entering(using):
254 | redis_enter_transaction_management(managed=False, using=using)
255 | managed(False, using=using)
256 |
257 | def exiting(exc_value, using):
258 | redis_leave_transaction_management(using=using)
259 |
260 | return _transaction_func(entering, exiting, using)
261 |
262 | def commit_on_success(using=None):
263 | """
264 | This decorator activates commit on response. This way, if the view function
265 | runs successfully, a commit is made; if the viewfunc produces an exception,
266 | a rollback is made. This is one of the most common ways to do transaction
267 | control in Web apps.
268 | """
269 | def entering(using):
270 | redis_enter_transaction_management(using=using)
271 | managed(True, using=using)
272 |
273 | def exiting(exc_value, using):
274 | try:
275 | if exc_value is not None:
276 | if is_dirty(using=using):
277 | rollback(using=using)
278 | else:
279 | if is_dirty(using=using):
280 | try:
281 | redis_commit(using=using)
282 | except:
283 | rollback(using=using)
284 | raise
285 | finally:
286 | redis_leave_transaction_management(using=using)
287 |
288 | return _transaction_func(entering, exiting, using)
289 |
290 | def commit_manually(using=None):
291 | """
292 | Decorator that activates manual transaction control. It just disables
293 | automatic transaction control and doesn't do any commit/rollback of its
294 | own -- it's up to the user to call the commit and rollback functions
295 | themselves.
296 | """
297 | def entering(using):
298 | redis_enter_transaction_management(using=using)
299 | #managed(True, using=using)
300 |
301 | def exiting(exc_value, using):
302 | redis_leave_transaction_management(using=using)
303 |
304 | return _transaction_func(entering, exiting, using)
305 |
--------------------------------------------------------------------------------
/django_redis_engine/serializer.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.db.models.query import QuerySet
3 | from django.utils.functional import SimpleLazyObject
4 | from django.utils.importlib import import_module
5 |
6 | def get_model_by_meta(model_meta):
7 | app, model = model_meta['_app'], model_meta['_model']
8 | try:
9 | module = import_module(app + '.models')
10 | except ImportError:
11 | return models.get_model(app, model)
12 | else:
13 | try:
14 | return getattr(module, model)
15 | except AttributeError:
16 | raise AttributeError("Could not find model %r in module %r" % (model, module))
17 |
18 | class LazyModelInstance(SimpleLazyObject):
19 | """
20 | Lazy model instance.
21 | """
22 | def __init__(self, model, pk):
23 | self.__dict__['_pk'] = pk
24 | self.__dict__['_model'] = model
25 | super(LazyModelInstance, self).__init__(self._load_data)
26 |
27 | def _load_data(self):
28 | return self._model.objects.get(pk=self._pk)
29 |
30 | def __eq__(self, other):
31 | if isinstance(other, LazyModelInstance):
32 | return self.__dict__['_pk'] == other.__dict__['_pk'] and \
33 | self.__dict__['_model'] == other.__dict__['_model']
34 | return super(LazyModelInstance, self).__eq__(other)
35 |
36 |
37 | class TransformDjango(SONManipulator):
38 | def transform_incoming(self, value, collection):
39 | if isinstance(value, (list, tuple, set, QuerySet)):
40 | return [self.transform_incoming(item, collection) for item in value]
41 |
42 | if isinstance(value, dict):
43 | return dict((key, self.transform_incoming(subvalue, collection))
44 | for key, subvalue in value.iteritems())
45 |
46 | if isinstance(value, models.Model):
47 | value.save()
48 | return {
49 | '_app' : value._meta.app_label,
50 | '_model' : value._meta.object_name,
51 | 'pk' : value.pk,
52 | '_type' : 'django'
53 | }
54 |
55 | return value
56 |
57 | def transform_outgoing(self, son, collection):
58 | if isinstance(son, (list, tuple, set)):
59 | return [self.transform_outgoing(value, collection) for value in son]
60 |
61 | if isinstance(son, dict):
62 | if son.get('_type') == 'django':
63 | return LazyModelInstance(get_model_by_meta(son), son['pk'])
64 | else:
65 | return dict((key, self.transform_outgoing(value, collection))
66 | for key, value in son.iteritems())
67 | return son
68 |
--------------------------------------------------------------------------------
/normalize_whitespace:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Removes trailing whitespace from the file in sys.argv[1]
4 | Example:
5 | ./normalize_whitespace foo.py
6 |
7 | or:
8 | find -type f | xargs -n1 ./normalize_whitespace
9 | """
10 | import re
11 | import sys
12 | with open(sys.argv[1]) as f: c = f.read().split('\n')
13 | with open(sys.argv[1], 'w') as f:
14 | for line in c:
15 | if not line.isspace():
16 | f.write(line.rstrip())
17 | f.write('\n')
18 | f.truncate(f.tell()-1)
19 |
20 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import codecs
3 | import django_redis_engine as distmeta
4 |
5 |
6 | CLASSIFIERS = [
7 | 'Intended Audience :: Developers',
8 | 'License :: OSI Approved :: BSD License',
9 | 'Programming Language :: Python',
10 | 'Topic :: Internet',
11 | 'Topic :: Database',
12 | 'Topic :: Software Development :: Libraries :: Python Modules',
13 | 'Operating System :: OS Independent',
14 | ]
15 |
16 | for ver in ['2', '2.4', '2.5', '2.6', '2.7']:
17 | CLASSIFIERS.append('Programming Language :: Python :: %s' % ver)
18 |
19 |
20 | setup(
21 | name='django-redis-engine',
22 | version='.'.join(map(str, distmeta.__version__)),
23 | author=distmeta.__author__,
24 | author_email=distmeta.__contact__,
25 | license='2-clause BSD',
26 | description= "A Redis backend.",
27 | long_description=codecs.open('README.rst', 'r', 'utf-8').read(),
28 |
29 | platforms=['any'],
30 | install_requires=['django>=1.2', 'djangotoolbox'],
31 |
32 | packages=find_packages(exclude=['testproject']),
33 | include_package_data=True,
34 | classifiers=CLASSIFIERS,
35 | )
36 |
--------------------------------------------------------------------------------
/testproject/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MirkoRossini/django-redis-engine/fc6fddbcf080b0652efb728314e67b21d4b8186a/testproject/__init__.py
--------------------------------------------------------------------------------
/testproject/dbindexes.py:
--------------------------------------------------------------------------------
1 | # dbindexes.py:
2 | import dbindexer
3 | dbindexer.autodiscover()
4 |
--------------------------------------------------------------------------------
/testproject/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | try:
4 | import settings # Assumed to be in the same directory.
5 | except ImportError:
6 | import sys
7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
8 | sys.exit(1)
9 |
10 | if __name__ == "__main__":
11 | execute_manager(settings)
12 |
--------------------------------------------------------------------------------
/testproject/settings.py:
--------------------------------------------------------------------------------
1 | # Initialize App Engine and import the default settings (DB backend, etc.).
2 | # If you want to use a different backend you have to remove all occurences
3 | # of "djangoappengine" from this file.
4 | #from djangoappengine.settings_base import *
5 | import django.conf.global_settings as DEFAULT_SETTINGS
6 | import os
7 |
8 | SECRET_KEY = '=r-$b*8hglm+858&9t043hlm6-&6-3d3vfc4((7yd0dbrakhvi'
9 |
10 | DATABASES = {
11 | 'default': {
12 | 'ENGINE': 'django_redis_engine',
13 | 'NAME': '',
14 | 'USER': '',
15 | 'PASSWORD': '',
16 | 'HOST': 'localhost',
17 | 'PORT': 6379,
18 |
19 | }
20 | }
21 |
22 |
23 | DBINDEXER_SITECONF = 'dbindexes'
24 |
25 | DBINDEXER_BACKENDS = (
26 | 'dbindexer.backends.FKNullFix',
27 | 'dbindexer.backends.BaseResolver',
28 | 'dbindexer.backends.InMemoryJOINResolver',
29 | )
30 |
31 |
32 |
33 | INSTALLED_APPS = (
34 | 'django.contrib.auth',
35 | 'django.contrib.contenttypes',
36 | 'django.contrib.sessions',
37 | 'djangotoolbox',
38 | 'testapp',
39 |
40 | )
41 |
42 | # This test runner captures stdout and associates tracebacks with their
43 | # corresponding output. Helps a lot with print-debugging.
44 | TEST_RUNNER = 'djangotoolbox.test.CapturingTestSuiteRunner'
45 |
46 | TIME_ZONE = 'America/Chicago'
47 |
48 | LANGUAGE_CODE = 'en-us'
49 |
50 | ADMIN_MEDIA_PREFIX = '/media/admin/'
51 |
52 |
53 | MEDIA_URL = '/media/'
54 | TEMPLATE_DIRS = (
55 | os.path.join(os.path.dirname(__file__), 'templates'),
56 | os.path.join(os.path.dirname(__file__), 'unimia/templates'),
57 |
58 | )
59 |
60 |
61 | REDIS_SETTINGS_MODULES = ('testapp.redis_settings',)
62 |
63 |
64 | ROOT_URLCONF = 'urls'
65 |
66 | SITE_ID = 1
67 |
68 | #GAE_SETTINGS_MODULES = (
69 | # 'unimia.gae_settings',
70 | #)
71 |
72 | TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
73 | 'django.core.context_processors.request',
74 | )
75 |
76 | MIDDLEWARE_CLASSES = (
77 | 'django.middleware.common.CommonMiddleware',
78 | 'django.contrib.sessions.middleware.SessionMiddleware',
79 | 'django.middleware.csrf.CsrfViewMiddleware',
80 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
81 | 'dbindexer.middleware.DBIndexerMiddleware',
82 | )
83 | #) + DEFAULT_SETTINGS.MIDDLEWARE_CLASSES
84 |
85 |
86 | # Activate django-dbindexer if available
87 | try:
88 | import dbindexer
89 | DATABASES['native'] = DATABASES['default']
90 | DATABASES['default'] = {'ENGINE': 'dbindexer', 'TARGET': 'native'}
91 | INSTALLED_APPS += ('dbindexer',)
92 | except ImportError,e:
93 | import traceback
94 | traceback.print_exc(20)
95 |
96 |
97 | DEBUG = True
98 |
--------------------------------------------------------------------------------
/testproject/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block title %}Page not found{% endblock %}
3 |
4 | {% block content %}
5 | The page you requested could not be found.
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/testproject/templates/500.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block title %}Server error{% endblock %}
3 |
4 | {% block content %}
5 | There was an error while handling your request.
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/testproject/templates/aproposito.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block content %}
3 |
4 |
5 |
A proposito di La Mia Uni
6 |
7 |
8 |
9 |
10 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/testproject/templates/base.html:
--------------------------------------------------------------------------------
1 |
4 |
8 |
9 | Redis-testapp
10 |
11 |
12 |
13 |
14 |
15 |
16 | {% block content %}{% endblock %}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/testproject/templates/come_aiutare.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block content %}
3 |
4 |
5 |
Come aiutare
6 |
7 |
8 |
9 |
10 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/testproject/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block content %}
3 | {% endblock %}
4 |
--------------------------------------------------------------------------------
/testproject/templates/login.html:
--------------------------------------------------------------------------------
1 | {% if user.is_authenticated %}
2 | {# display something else here... #}
3 | {% else %}
4 |
10 | {% endif %}
11 |
12 |
--------------------------------------------------------------------------------
/testproject/templates/menu.html:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/testproject/templates/pagination.html:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/testproject/templates/registration/login.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
4 | {% block content %}
5 |
6 | {% if form.errors %}
7 | Your username and password didn't match. Please try again.
8 | {% endif %}
9 |
10 |
20 |
21 | {% endblock %}
22 |
23 |
--------------------------------------------------------------------------------
/testproject/testapp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MirkoRossini/django-redis-engine/fc6fddbcf080b0652efb728314e67b21d4b8186a/testproject/testapp/__init__.py
--------------------------------------------------------------------------------
/testproject/testapp/dbindexes.py:
--------------------------------------------------------------------------------
1 | from models import Post
2 | from dbindexer.api import register_index
3 |
4 |
5 | register_index(Post, {'title': 'contains',})
6 |
--------------------------------------------------------------------------------
/testproject/testapp/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from models import *
3 |
4 |
5 | class MyForm(forms.ModelForm):
6 | class Meta:
7 | model = Post
8 |
9 |
10 | class AnswerForm(forms.ModelForm):
11 | class Meta:
12 | model = Answer
13 | exclude = ("post")
14 |
15 |
--------------------------------------------------------------------------------
/testproject/testapp/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 | # Create your models here.
4 |
5 | class Post(models.Model):
6 | title = models.CharField(max_length = 100)
7 | text = models.TextField()
8 | time = models.DateTimeField(auto_now_add = True,auto_now = True)
9 |
10 | class Answer(models.Model):
11 | text = models.TextField()
12 | post = models.ForeignKey(Post)
13 |
--------------------------------------------------------------------------------
/testproject/testapp/redis_settings.py:
--------------------------------------------------------------------------------
1 | from models import Post
2 | from django.contrib.sessions.models import Session
3 |
4 |
5 | INDEXES = {
6 | Post: {'idxf_title_l_contains': ('startswith',),
7 | 'text':('iendswith',),
8 | 'title':('startswith','endswith'),
9 | 'time':('gt','lte'),
10 | },
11 | Session : {'expire_date' : ('gt',)
12 | },
13 |
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/testproject/testapp/templates/testapp/.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 | {% endblock %}
5 |
--------------------------------------------------------------------------------
/testproject/testapp/templates/testapp/add_answer.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/testproject/testapp/templates/testapp/add_post.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/testproject/testapp/templates/testapp/forms/AnswerForm.html:
--------------------------------------------------------------------------------
1 | {{ form.non_field_errors }}
2 |
3 |
4 | {{ form.text.errors }}
5 | {{form.text.label_tag}}
6 | {{form.text}}
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/testproject/testapp/templates/testapp/forms/MyForm.html:
--------------------------------------------------------------------------------
1 | {{ form.non_field_errors }}
2 |
3 |
4 | {{ form.title.errors }}
5 | {{form.title.label_tag}}
6 | {{form.title}}
7 |
8 |
9 |
10 | {{ form.text.errors }}
11 | {{form.text.label_tag}}
12 | {{form.text}}
13 |
14 |
--------------------------------------------------------------------------------
/testproject/testapp/templates/testapp/posts.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block content %}
3 | Add new post
4 | Filter by title:
9 |
10 | {% if filter %} Show all
{% endif %}
11 |
12 | {% for post in posts %}
13 |
14 |
15 |
{{post.title}}
16 |
{{post.text|linebreaks}}
17 |
18 |
19 | {% for answer in post.answer_set.all %}
20 |
21 |
{{answer.text|linebreaks}}
22 |
23 | {% endfor %}
24 |
Add answer
25 |
26 |
27 | {% endfor %}
28 |
29 |
30 | {% endblock %}
31 |
--------------------------------------------------------------------------------
/testproject/testapp/tests.py:
--------------------------------------------------------------------------------
1 | #-*- encoding:utf-8 -*-
2 |
3 | from django.test import TestCase
4 | from models import *
5 | from md5 import md5
6 | import random
7 |
8 | from django_redis_engine import redis_transaction
9 | from django.db import transaction
10 | class SimpleTest(TestCase):
11 |
12 | def test_update_and_filters(self):
13 | """
14 | test effects of updates on filters
15 | """
16 | post = Post.objects.create(
17 | text = "to be updated text",
18 | title = "to be updated title"
19 | )
20 | post.text = "updated text"
21 | post.save()
22 | posts = Post.objects.filter(text = "to be updated text")
23 | self.failUnlessEqual(len(posts), 0)
24 | posts = Post.objects.filter(text = "updated text")
25 | self.failUnlessEqual(len(posts), 1)
26 | post.title = "updated title"
27 | post.save()
28 | posts = Post.objects.filter(title__contains = "to be updated title")
29 | self.failUnlessEqual(len(posts), 0)
30 | posts = Post.objects.filter(title__contains = "updated title")
31 | self.failUnlessEqual(len(posts), 1)
32 | post.delete()
33 |
34 | def atest_insert_transaction(self):
35 | """
36 | stress test, used to find out how much network latency
37 | affects performance using transactions.
38 | """
39 | n = 100
40 | ng = 100
41 | ngc = 100
42 | l = []
43 | #print redis_transaction.commit_manually(using = 'native')
44 | #print transaction.commit_manually()
45 | with redis_transaction.commit_manually(using = 'native'):
46 | print "begin"
47 | for i in range(n):
48 | tit = md5(str(random.random())+str(i)).hexdigest()
49 | l.append(tit)
50 | Post.objects.create(
51 | title = tit,
52 | text = " ".join(
53 | [md5(
54 | str(random.random())+\
55 | str(t)+\
56 | str(i)).hexdigest() for t in range(20)]
57 | )
58 | )
59 | redis_transaction.commit()
60 | for i in range(ng):
61 | p = Post.objects.get(title = l[random.randint(0,n-1)] )
62 | for i in range(ngc):
63 | Post.objects.filter(title__contains = l[random.randint(0,n-1)])
64 | #self.failUnlessEqual(len(posts), 1)
65 |
66 | def test_add_post_answers_and_filters(self):
67 | """
68 | Create some posts, create answers to them,
69 | test contains filter on post title
70 | test startswith filter on post title
71 | test endswith filter on post title
72 | test iendswith filter on post text
73 | test exact filter on all fields
74 | test gt and lte filters
75 | test deletion of objects and indexes
76 | """
77 | post1 = Post.objects.create(
78 | text = "text1",
79 | title = "title1"
80 | )
81 |
82 | post2 = Post.objects.create(
83 | text = "text2",
84 | title = "title2"
85 | )
86 | post3 = Post.objects.create(
87 | text = "RrRQqà",
88 | title = "AaABbB"
89 | )
90 | answer1 = Answer.objects.create(
91 | text= "answer1 to post 1",
92 | post = post1
93 | )
94 | answer2 = Answer.objects.create(
95 | text= "answer2 to post 1",
96 | post = post1
97 | )
98 | answer3 = Answer.objects.create(
99 | text= "answer1 to post 2",
100 | post = post2
101 | )
102 | posts = Post.objects.all()
103 | self.failUnlessEqual(len(posts), 3)
104 | posts = Post.objects.filter(title__contains = 'title')
105 | self.failUnlessEqual(len(posts), 2)
106 | p = Post.objects.get(title = 'title1')
107 | self.failUnlessEqual(p.pk, post1.pk)
108 | p = Post.objects.get(text = 'text2')
109 | self.failUnlessEqual(p.pk, post2.pk)
110 | a = Answer.objects.get(text = 'answer2 to post 1')
111 | self.failUnlessEqual(a.pk, answer2.pk)
112 |
113 | p.delete()
114 | posts = Post.objects.all()
115 | self.failUnlessEqual(len(posts), 2)
116 | posts = Post.objects.filter(title__contains = 'title')
117 | self.failUnlessEqual(len(posts), 1)
118 | posts = Post.objects.filter(title__startswith = 'Aa')
119 | self.failUnlessEqual(len(posts), 1)
120 | posts = Post.objects.filter(title__startswith = 'AA')
121 | self.failUnlessEqual(len(posts), 0)
122 |
123 | posts = Post.objects.filter(title__endswith = 'bB')
124 | self.failUnlessEqual(len(posts), 1)
125 |
126 | posts = Post.objects.filter(title__endswith = 'BB')
127 | self.failUnlessEqual(len(posts), 0)
128 | posts = Post.objects.filter(text__iendswith = 'qqà')
129 | self.failUnlessEqual(len(posts), 1)
130 | posts = Post.objects.filter(text__iendswith = 'QQÀ')
131 | self.failUnlessEqual(len(posts), 1)
132 |
133 |
134 | a.delete()
135 | answers = Answer.objects.filter(text = 'answer2 to post 1')
136 | self.failUnlessEqual(len(answers), 0)
137 |
138 | import datetime
139 | posts = Post.objects.filter(text__iendswith = 'QQÀ',
140 | time__gt = datetime.datetime.now() - datetime.timedelta(minutes = 10))
141 | self.failUnlessEqual(len(posts), 1)
142 | posts = Post.objects.filter(time__gt = datetime.datetime.now() - datetime.timedelta(minutes = 10))
143 | self.failUnlessEqual(len(posts), 2)
144 |
145 | posts = Post.objects.filter(time__lte = posts[0].time)
146 | self.failUnlessEqual(len(posts), 1)
147 |
148 | posts = Post.objects.filter(time__lte = posts[0].time + datetime.timedelta(minutes = 10))
149 | self.failUnlessEqual(len(posts), 2)
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/testproject/testapp/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 |
4 | urlpatterns = patterns('',
5 |
6 |
7 | url(r'^add_post/$', 'testapp.views.add_post', {},name = 'testapp_add_post'),
8 |
9 | url(r'^add_answer/(?P\d+)/$', 'testapp.views.add_answer', {},name = 'testapp_add_answer'),
10 |
11 | url(r'^posts/$', 'testapp.views.posts', {},name = 'testapp_posts'),
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/testproject/testapp/views.py:
--------------------------------------------------------------------------------
1 | # Create your views here.
2 | from models import Post, Answer
3 | from django.shortcuts import render_to_response
4 | from forms import *
5 | from django.template import RequestContext
6 | from django.http import HttpResponseRedirect
7 |
8 | def add_post(request,):
9 | #VIEW CODE
10 | if request.method == 'POST':
11 | form = MyForm(request.POST)
12 | if form.is_valid():
13 | # Process the data in form.cleaned_data
14 | form.save()
15 | return HttpResponseRedirect('/posts/')
16 | else:
17 | form = MyForm()
18 | ret_dict = {
19 | 'form':form,
20 | }
21 | return render_to_response("testapp/add_post.html",
22 | ret_dict,
23 | context_instance = RequestContext(request),)
24 |
25 |
26 |
27 | def add_answer(request,post_id):
28 | #VIEW CODE
29 | if request.method == 'POST':
30 | form = AnswerForm(request.POST)
31 | post = Post.objects.get(pk = post_id)
32 | if form.is_valid():
33 | # Process the data in form.cleaned_data
34 | form.instance.post = post
35 | form.save()
36 | return HttpResponseRedirect('/posts/')
37 | else:
38 | form = AnswerForm()
39 | ret_dict = {
40 | 'form':form,
41 | }
42 | return render_to_response("testapp/add_answer.html",
43 | ret_dict,
44 | context_instance = RequestContext(request),)
45 |
46 |
47 |
48 | def posts(request,):
49 | ret_dict = {}
50 | if request.method == 'POST' and request.POST['title_filter'] is not None and request.POST['title_filter'] != '':
51 | posts = Post.objects.filter(title__contains = request.POST['title_filter'])
52 | ret_dict['filter'] = request.POST['title_filter']
53 | else:
54 | posts = Post.objects.all()
55 |
56 | ret_dict['posts'] = posts
57 |
58 | return render_to_response("testapp/posts.html",
59 | ret_dict,
60 | context_instance = RequestContext(request),)
61 |
62 |
63 |
--------------------------------------------------------------------------------
/testproject/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | #handler500 = 'djangotoolbox.errorviews.server_error'
4 |
5 | urlpatterns = patterns('',
6 |
7 | ('', include('testapp.urls')),
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------