d73abd141c7c57e186527296a4eaebfdbf69bf17
[wolnelektury.git] / apps / djangosphinx / utils / config.py
1 from django.conf import settings
2 from django.template import Template, Context
3
4 from django.db import models
5 from django.contrib.contenttypes.models import ContentType
6
7 import os.path
8
9 import djangosphinx.apis.current as sphinxapi
10
11 __all__ = ('generate_config_for_model', 'generate_config_for_models')
12
13 def _get_database_engine():
14     if settings.DATABASE_ENGINE == 'mysql':
15         return settings.DATABASE_ENGINE
16     elif settings.DATABASE_ENGINE.startswith('postgresql'):
17         return 'pgsql'
18     raise ValueError, "Only MySQL and PostgreSQL engines are supported by Sphinx."
19
20 def _get_template(name):
21     paths = (
22         os.path.join(os.path.dirname(__file__), '../apis/api%s/templates/' % (sphinxapi.VER_COMMAND_SEARCH,)),
23         os.path.join(os.path.dirname(__file__), '../templates/'),
24     )
25     for path in paths:
26         try:
27             fp = open(path + name, 'r')
28         except IOError:
29             continue
30         try:
31             t = Template(fp.read())
32             return t
33         finally:
34             fp.close()
35     raise ValueError, "Template matching name does not exist: %s." % (name,)
36
37 def _is_sourcable_field(field):
38     # We can use float fields in 0.98
39     if sphinxapi.VER_COMMAND_SEARCH >= 0x113 and (isinstance(field, models.FloatField) or isinstance(field, models.DecimalField)):
40         return True
41     if isinstance(field, models.ForeignKey):
42         return True
43     if isinstance(field, models.IntegerField) and field.choices:
44         return True
45     if not field.rel:
46         return True
47     return False
48
49 # No trailing slashes on paths
50 DEFAULT_SPHINX_PARAMS = {
51     'database_engine': _get_database_engine(),
52     'database_host': settings.DATABASE_HOST,
53     'database_port': settings.DATABASE_PORT,
54     'database_name': settings.DATABASE_NAME,
55     'database_user': settings.DATABASE_USER,
56     'database_password': settings.DATABASE_PASSWORD,
57     'log_file': '/var/log/sphinx/searchd.log',
58     'data_path': '/var/data',
59 }
60
61 # Generate for single models
62
63 def generate_config_for_model(model_class, index=None, sphinx_params={}):
64     """
65     Generates a sample configuration including an index and source for
66     the given model which includes all attributes and date fields.
67     """
68     return generate_source_for_model(model_class, index, sphinx_params) + "\n\n" + generate_index_for_model(model_class, index, sphinx_params)
69
70 def generate_index_for_model(model_class, index=None, sphinx_params={}):
71     """Generates a source configmration for a model."""
72     t = _get_template('index.conf')
73     
74     if index is None:
75         index = model_class._meta.db_table
76     
77     params = DEFAULT_SPHINX_PARAMS
78     params.update(sphinx_params)
79     params.update({
80         'index_name': index,
81         'source_name': index,
82     })
83     
84     c = Context(params)
85     
86     return t.render(c)
87     
88
89 def generate_source_for_model(model_class, index=None, sphinx_params={}):
90     """Generates a source configmration for a model."""
91     t = _get_template('source.conf')
92     
93     valid_fields = [f for f in model_class._meta.fields if _is_sourcable_field(f)]
94     
95     # Hackish solution for a bug I've introduced into composite pks branch
96     pk = model_class._meta.get_field(model_class._meta.pk.name)
97     
98     if pk not in valid_fields:
99         valid_fields.insert(0, model_class._meta.pk)
100     
101     if index is None:
102         index = model_class._meta.db_table
103     
104     params = DEFAULT_SPHINX_PARAMS
105     params.update(sphinx_params)
106     params.update({
107         'source_name': index,
108         'index_name': index,
109         'table_name': index,
110         'primary_key': pk.column,
111         'field_names': [f.column for f in valid_fields],
112         'group_columns': [f.column for f in valid_fields if (f.rel or isinstance(f, models.BooleanField) or isinstance(f, models.IntegerField)) and not f.primary_key],
113         'date_columns': [f.column for f in valid_fields if isinstance(f, models.DateTimeField) or isinstance(f, models.DateField)],
114         'float_columns': [f.column for f in valid_fields if isinstance(f, models.FloatField) or isinstance(f, models.DecimalField)],
115     })
116     
117     c = Context(params)
118     
119     return t.render(c)
120     
121 # Generate for multiple models (search UNIONs)
122
123 def generate_config_for_models(model_classes, index=None, sphinx_params={}):
124     """
125     Generates a sample configuration including an index and source for
126     the given model which includes all attributes and date fields.
127     """
128     return generate_source_for_models(model_classes, index, sphinx_params) + "\n\n" + generate_index_for_models(model_classes, index, sphinx_params)
129
130 def generate_index_for_models(model_classes, index=None, sphinx_params={}):
131     """Generates a source configmration for a model."""
132     t = _get_template('index-multiple.conf')
133     
134     if index is None:
135         index = '_'.join(m._meta.db_table for m in model_classes)
136     
137     params = DEFAULT_SPHINX_PARAMS
138     params.update(sphinx_params)
139     params.update({
140         'index_name': index,
141         'source_name': index,
142     })
143     
144     c = Context(params)
145     
146     return t.render(c)
147
148 def generate_source_for_models(model_classes, index=None, sphinx_params={}):
149     """Generates a source configmration for a model."""
150     t = _get_template('source-multiple.conf')
151     
152     # We need to loop through each model and find only the fields that exist *exactly* the
153     # same across models.
154     def _the_tuple(f):
155         return (f.__class__, f.column, getattr(f.rel, 'to', None), f.choices)
156     
157     valid_fields = [_the_tuple(f) for f in model_classes[0]._meta.fields if _is_sourcable_field(f)]
158     for model_class in model_classes[1:]:
159         valid_fields = [_the_tuple(f) for f in model_class._meta.fields if _the_tuple(f) in valid_fields]
160     
161     tables = []
162     for model_class in model_classes:
163         tables.append((model_class._meta.db_table, ContentType.objects.get_for_model(model_class)))
164     
165     if index is None:
166         index = '_'.join(m._meta.db_table for m in model_classes)
167     
168     params = DEFAULT_SPHINX_PARAMS
169     params.update(sphinx_params)
170     params.update({
171         'tables': tables,
172         'source_name': index,
173         'index_name': index,
174         'field_names': [f[1] for f in valid_fields],
175         'group_columns': [f[1] for f in valid_fields if f[2] or isinstance(f[0], models.BooleanField) or isinstance(f[0], models.IntegerField)],
176         'date_columns': [f[1] for f in valid_fields if issubclass(f[0], models.DateTimeField) or issubclass(f[0], models.DateField)],
177         'float_columns': [f[1] for f in valid_fields if isinstance(f[0], models.FloatField) or isinstance(f[0], models.DecimalField)],
178     })
179     
180     c = Context(params)
181     
182     return t.render(c)