From bd965883a57e6010da8e21bea4442412869c73eb Mon Sep 17 00:00:00 2001
From: janek@kublik <janek@kublik>
Date: Sun, 13 Dec 2015 13:00:44 +0100
Subject: [PATCH] osobna aplikacja do tabelek

---
 dictionary/ajax_lexeme_view.py                     | 57 +++++++++------------------------------------------------
 dictionary/management/commands/export_template.py  | 68 --------------------------------------------------------------------
 dictionary/management/commands/export_templates.py | 31 -------------------------------
 dictionary/management/commands/import_template.py  | 74 --------------------------------------------------------------------------
 dictionary/management/commands/import_templates.py | 41 -----------------------------------------
 dictionary/models.py                               |  2 +-
 dictionary/templates/inflection_table.html         | 60 ------------------------------------------------------------
 dictionary/urls.py                                 |  1 -
 dictionary/util.py                                 | 33 ---------------------------------
 management/templates/table_preview.html            |  3 ---
 settings.py                                        |  1 +
 tables/__init__.py                                 |  0
 tables/management/__init__.py                      |  0
 tables/management/commands/__init__.py             |  0
 tables/management/commands/export_template.py      | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tables/management/commands/export_templates.py     | 32 ++++++++++++++++++++++++++++++++
 tables/management/commands/import_template.py      | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tables/management/commands/import_templates.py     | 43 +++++++++++++++++++++++++++++++++++++++++++
 tables/migrations/__init__.py                      |  0
 tables/models.py                                   |  3 +++
 tables/templates/inflection_table.html             | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tables/templates/table_preview.html                |  3 +++
 tables/tests.py                                    |  3 +++
 tables/urls.py                                     | 10 ++++++++++
 tables/util.py                                     | 34 ++++++++++++++++++++++++++++++++++
 tables/views.py                                    | 45 +++++++++++++++++++++++++++++++++++++++++++++
 urls.py                                            |  1 +
 27 files changed, 387 insertions(+), 360 deletions(-)
 delete mode 100644 dictionary/management/commands/export_template.py
 delete mode 100644 dictionary/management/commands/export_templates.py
 delete mode 100644 dictionary/management/commands/import_template.py
 delete mode 100644 dictionary/management/commands/import_templates.py
 delete mode 100644 dictionary/templates/inflection_table.html
 delete mode 100644 management/templates/table_preview.html
 create mode 100644 tables/__init__.py
 create mode 100644 tables/management/__init__.py
 create mode 100644 tables/management/commands/__init__.py
 create mode 100644 tables/management/commands/export_template.py
 create mode 100644 tables/management/commands/export_templates.py
 create mode 100644 tables/management/commands/import_template.py
 create mode 100644 tables/management/commands/import_templates.py
 create mode 100644 tables/migrations/__init__.py
 create mode 100644 tables/models.py
 create mode 100644 tables/templates/inflection_table.html
 create mode 100644 tables/templates/table_preview.html
 create mode 100644 tables/tests.py
 create mode 100644 tables/urls.py
 create mode 100644 tables/util.py
 create mode 100644 tables/views.py

diff --git a/dictionary/ajax_lexeme_view.py b/dictionary/ajax_lexeme_view.py
index d75bb4c..e72d91d 100644
--- a/dictionary/ajax_lexeme_view.py
+++ b/dictionary/ajax_lexeme_view.py
@@ -1,26 +1,24 @@
 # -*- coding: utf-8 -*-
-from django.utils import timezone
-
 from django.core.cache import cache
+from django.db.models import Max
 from django.shortcuts import get_object_or_404
+from django.utils import timezone
 from django.utils.encoding import force_unicode
 from django.utils.translation import ugettext as _
-from django.db.models import Max
 
+from common.decorators import ajax, AjaxError, render_ajax
+from common.util import error_messages, bisect_left, format_date
 from dictionary.ajax_lexeme_slickgrid import LexemeQuery
 from dictionary.auto_derivatives import lexeme_derivatives, create_derivative
-from dictionary.models import Lexeme, LexemeInflectionPattern, PartOfSpeech, \
-    Vocabulary, Qualifier, ClassificationValue, CrossReference, InputLexeme, \
-    CrossReferenceType, LexemeAttributeValue, Gender, LexemeAttribute, \
-    LexemeAV, LexemeCV, LexemeList
-from dictionary.util import prepare_table
-from patterns.models import Pattern, Ending
 from dictionary.forms import LexemeEditForm, LIPEditForm, ClassificationForm, \
     CrossReferenceForm, ActionFieldForm, ACTION_FIELDS,\
     LexemeOpenAttributeForm, LexemeClosedAttributeForm, \
     LexemeMultipleAttributeForm
-from common.decorators import ajax, AjaxError, render_ajax
-from common.util import error_messages, bisect_left, format_date
+from dictionary.models import Lexeme, LexemeInflectionPattern, PartOfSpeech, \
+    Vocabulary, Qualifier, ClassificationValue, CrossReference, InputLexeme, \
+    CrossReferenceType, LexemeAttributeValue, Gender, LexemeAttribute, \
+    LexemeAV, LexemeCV, LexemeList
+from patterns.models import Ending
 
 
 @render_ajax(
@@ -56,43 +54,6 @@ def inflection_tables(request, variant, lexeme_id):
     }
 
 
-@render_ajax(template='table_preview.html', method='get')
-def table_preview(request, lexeme_id, lip_id, pattern, attr_data=None,
-                  gender=None, entry=None, pos=None):
-    lexeme = Lexeme.all_objects.get(pk=lexeme_id)
-    if not lexeme.perm(request.user, 'view'):
-        raise AjaxError('access denied')
-    if pos is not None:
-        part_of_speech = PartOfSpeech.objects.get(symbol=pos)
-    else:
-        part_of_speech = lexeme.part_of_speech
-    try:
-        if entry is not None:
-            lexeme.entry = entry
-        pattern = Pattern.objects.get(name=pattern)
-        gender = Gender.objects.get(id=gender) if gender else None
-        if attr_data:
-            attr_vals = LexemeAttributeValue.objects.filter(
-                id__in=attr_data)
-        else:
-            attr_vals = None
-        if lip_id.startswith('lip_add'):
-            lip = LexemeInflectionPattern(lexeme=lexeme, index=0)
-        else:
-            lip = LexemeInflectionPattern.objects.get(pk=int(lip_id[3:]))
-        lip.pattern = pattern
-        lip.gender = gender
-        lip.root = lip.get_root()
-        qualifiers = Qualifier.visible_qualifiers(request.user)
-        table = lip.inflection_table(
-            '0', separated=True, qualifiers=qualifiers, edit_view=True,
-            attr_vals=attr_vals, pos=part_of_speech)
-        prepare_table(table)
-    except Pattern.DoesNotExist:
-        table = None
-    return {'table': table, 'color_scheme': part_of_speech.color_scheme}
-
-
 @ajax(template='odm_forms.html', method='get')
 def odm_forms(request, lexeme_id):
     to_return = {}
diff --git a/dictionary/management/commands/export_template.py b/dictionary/management/commands/export_template.py
deleted file mode 100644
index b7de638..0000000
--- a/dictionary/management/commands/export_template.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.core.management.base import BaseCommand
-from common.util import json_encode, uniprint
-from dictionary.models import TableTemplate, Variant
-
-
-class Command(BaseCommand):
-    help = "Exports a table template to JSON."
-
-    def handle(self, name, variant, *args, **options):
-        tt = TableTemplate.objects.get(
-            name=name.decode('utf-8'), variant__id=variant)
-        uniprint(json_encode(export_template(tt)))
-
-
-def export_template(tt):
-    data = {'name': tt.name, 'variant': tt.variant.id}
-    if tt.variant.type == Variant.TYPE_TABLE:
-        data['table_cells'] = []
-        data['headers'] = []
-    else:
-        data['export_cells'] = []
-    table_cells = tt.table_cells.select_related(
-        'base_form_label__lexical_class').prefetch_related(
-        'pattern_types', 'attribute_values')
-    export_cells = tt.export_cells.select_related(
-        'base_form_label__lexical_class').prefetch_related(
-        'pattern_types', 'attribute_values')
-    headers = tt.headers.prefetch_related(
-        'pattern_types', 'attribute_values')
-    table_elements = [('table_cells', tc) for tc in table_cells]
-    table_elements += [('export_cells', ec) for ec in export_cells]
-    table_elements += [('headers', h) for h in headers]
-    for elem_type, elem in table_elements:
-        data_row = {
-            'pattern_types': list(
-                elem.pattern_types.values_list('symbol', 'lexical_class_id')),
-            'attr_vals': list(elem.attribute_values.values_list(
-                'value', 'attribute__name')),
-        }
-        if tt.takes_gender:
-            data_row['genders'] = list(
-                elem.genders.values_list('symbol', flat=True))
-        if elem_type in ('table_cells', 'headers'):
-            data_row.update({
-                'row': elem.row,
-                'col': elem.col,
-                'rowspan': elem.rowspan,
-                'colspan': elem.colspan,
-            })
-        if elem_type in ('table_cells', 'export_cells'):
-            data_row.update({
-                'bfl': (elem.base_form_label.symbol,
-                        elem.base_form_label.lexical_class.symbol),
-                'prefix': elem.prefix,
-                'suffix': elem.suffix,
-            })
-        if elem_type == 'table_cells':
-            data_row['index'] = elem.index
-        if elem_type == 'export_cells':
-            data_row['tag'] = elem.tag_template
-        if elem_type == 'headers':
-            data_row.update({
-                'label': elem.label,
-                'css_class': elem.css_class,
-            })
-        data[elem_type].append(data_row)
-    return data
diff --git a/dictionary/management/commands/export_templates.py b/dictionary/management/commands/export_templates.py
deleted file mode 100644
index e8a2304..0000000
--- a/dictionary/management/commands/export_templates.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.core.management.base import BaseCommand
-from common.util import uniprint, json_encode
-from dictionary.management.commands.export_template import export_template
-from dictionary.models import TableTemplate
-
-
-class Command(BaseCommand):
-    help = "Exports all templates to JSON."
-
-    def handle(self, *args, **options):
-        uniprint(json_encode(export_templates()))
-
-
-def export_templates():
-    data = []
-    for tt in TableTemplate.objects.all():
-        data_row = export_template(tt)
-        data_row['parts_of_speech'] = list(
-            tt.parts_of_speech.values_list('symbol', flat=True))
-        data_row['pattern_types'] = list(
-            tt.pattern_types.values_list('symbol', 'lexical_class_id'))
-        data_row['attributes'] = list(
-            tt.attributes.values_list('name', flat=True))
-        data_row['attribute_values'] = list(
-            tt.attribute_values.values_list('value', 'attribute__name'))
-        data_row['cell_attributes'] = list(
-            tt.cell_attributes.values_list('name', flat=True))
-        data_row['takes_gender'] = tt.takes_gender
-        data.append(data_row)
-    return data
\ No newline at end of file
diff --git a/dictionary/management/commands/import_template.py b/dictionary/management/commands/import_template.py
deleted file mode 100644
index 4d1cd84..0000000
--- a/dictionary/management/commands/import_template.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-import json
-from django.core.management.base import BaseCommand
-from dictionary.models import Variant, TableTemplate, TableCell, \
-    PatternType, LexemeAttributeValue, TableHeader, ExportCell, Gender
-from patterns.models import BaseFormLabel, PatternType
-
-
-class Command(BaseCommand):
-    help = "Imports a table template from JSON."
-
-    def handle(self, filename, *args, **options):
-        import_template(json.load(open(filename)))
-
-PATTERN_TYPES = dict(
-    ((pt.symbol, pt.lexical_class.symbol), pt)
-    for pt in PatternType.objects.all())
-
-GENDERS = dict((g.symbol, g) for g in Gender.objects.all())
-
-ATTR_VALS = dict(
-    ((av.value, av.attribute.name), av)
-    for av in LexemeAttributeValue.objects.all())
-
-BFLS = dict(
-    ((bfl.symbol, bfl.lexical_class.symbol), bfl)
-    for bfl in BaseFormLabel.objects.all())
-
-
-def get_bfl(data):
-    return BFLS[tuple(data['bfl'])]
-
-
-def import_template(data):
-    def add_restrictions(x, x_data):
-        x.pattern_types = (PATTERN_TYPES[tuple(pt_data)]
-                           for pt_data in x_data['pattern_types'])
-        if 'genders' in x_data:
-            x.genders = (GENDERS[gender] for gender in x_data['genders'])
-        x.attribute_values = (
-            ATTR_VALS[tuple(av_data)] for av_data in x_data['attr_vals'])
-
-    variant = Variant.objects.get(id=data['variant'])
-    tt, created = TableTemplate.objects.get_or_create(
-        variant=variant, name=data['name'])
-    if not created:
-        tt.table_cells.all().delete()
-        tt.headers.all().delete()
-        tt.export_cells.all().delete()
-    for tc_data in data.get('table_cells', []):
-        tc = TableCell(
-            table_template=tt,
-            row=tc_data['row'], col=tc_data['col'], rowspan=tc_data['rowspan'],
-            colspan=tc_data['colspan'], index=tc_data['index'],
-            base_form_label=get_bfl(tc_data), prefix=tc_data['prefix'],
-            suffix=tc_data['suffix'])
-        tc.save()
-        add_restrictions(tc, tc_data)
-    for h_data in data.get('headers', []):
-        th = TableHeader(
-            table_template=tt,
-            row=h_data['row'], col=h_data['col'], rowspan=h_data['rowspan'],
-            colspan=h_data['colspan'], label=h_data['label'],
-            css_class=h_data['css_class'])
-        th.save()
-        add_restrictions(th, h_data)
-    for ec_data in data.get('export_cells', []):
-        ec = ExportCell(
-            table_template=tt,
-            base_form_label=get_bfl(ec_data), prefix=ec_data['prefix'],
-            suffix=ec_data['suffix'], tag_template=ec_data['tag'])
-        ec.save()
-        add_restrictions(ec, ec_data)
-    return tt
\ No newline at end of file
diff --git a/dictionary/management/commands/import_templates.py b/dictionary/management/commands/import_templates.py
deleted file mode 100644
index 7b19235..0000000
--- a/dictionary/management/commands/import_templates.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-import json
-from django.core.management.base import BaseCommand
-from dictionary.management.commands.import_template import import_template
-from dictionary.models import TableTemplate, LexemeAttribute, \
-    LexemeAttributeValue
-from patterns.models import PatternType
-
-
-class Command(BaseCommand):
-    help = "Imports templated from JSON."
-
-    def handle(self, filename, *args, **options):
-        import_templates(json.load(open(filename)))
-
-PATTERN_TYPES = dict(
-    ((pt.symbol, pt.lexical_class.symbol), pt)
-    for pt in PatternType.objects.all())
-
-ATTRS = dict((attr.name, attr) for attr in LexemeAttribute.objects.all())
-
-ATTR_VALS = dict(
-    ((av.value, av.attribute.name), av)
-    for av in LexemeAttributeValue.objects.all())
-
-
-def import_templates(data):
-    TableTemplate.objects.all().delete()
-    for tt_data in data:
-        tt = import_template(tt_data)
-        tt.parts_of_speech = tt_data['parts_of_speech']
-        tt.pattern_types = (PATTERN_TYPES[tuple(pt_data)]
-                            for pt_data in tt_data['pattern_types'])
-        tt.attributes = (ATTRS[attr_name]
-                         for attr_name in tt_data['attributes'])
-        for av_data in tt_data['attribute_values']:
-            tt.attribute_values.add(ATTR_VALS[tuple(av_data)])
-        for attr_name in tt_data['cell_attributes']:
-            tt.cell_attributes.add(ATTRS[attr_name])
-        tt.takes_gender = tt_data['takes_gender']
-        tt.save()
\ No newline at end of file
diff --git a/dictionary/models.py b/dictionary/models.py
index 219ebfe..cdb31f4 100644
--- a/dictionary/models.py
+++ b/dictionary/models.py
@@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _, get_language
 from accounts.util import users_with_perm
 from common.models import NotDeletedManager
 from common.util import no_history
-from dictionary.util import prepare_table
+from tables.util import prepare_table
 from patterns.models import InflectionType, BaseFormLabel, PatternType, Pattern
 
 
diff --git a/dictionary/templates/inflection_table.html b/dictionary/templates/inflection_table.html
deleted file mode 100644
index d906672..0000000
--- a/dictionary/templates/inflection_table.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<div class="scheme{{ color_scheme }}">
-    <table class="inflection-table">
-        {% if tables and tables|length > 1 %}
-            <caption>
-                {{ gender.symbol }}:
-                {% if pronunciations %}
-                    <span class="lip-pronunciation">[{{ pronunciations|join:"/"|safe }}]{% if gender_qualifiers %};{% endif %}</span>
-                {% endif %}
-                <span class="qualifiers">{{ gender_qualifiers|join:" " }}</span>
-                {% include "pattern_list.html" %}
-            </caption>
-        {% endif %}
-        {% for row in table %}
-            <tr>
-                {% for cell in row %}
-                    {% if cell.type != "span" %}
-                        {% if cell.type == "empty" %}
-                            <td class="empty"></td>
-                        {% else %}
-                            {% if cell.type == "forms" %}
-                                <td rowspan="{{ cell.rowspan }}"
-                                    colspan="{{ cell.colspan }}"
-                                    {% if cell.colspan > 1 %}class="data-c"{% endif %}>
-                                    <ul class="form-list">
-                                        {% for entry in cell.forms %}
-                                            <li>
-                                                <span class="form {% for p in entry.patterns %}p{{ p.id }} {% endfor %}">
-                                                    {{ entry.form|safe }}
-                                                </span>
-                                                <span class="qualifiers">
-                                                  {% for q in entry.qualifiers %}
-                                                      {{ q.label }}
-                                                  {% endfor %}
-                                                </span>
-                                            </li>
-                                        {% endfor %}
-                                    </ul>
-                                </td>
-                            {% else %}
-                                <td rowspan="{{ cell.rowspan }}"
-                                    colspan="{{ cell.colspan }}"
-                                    {% if cell.colspan > 1 and cell.css_class == 'data' %}
-                                    class="data-c"
-                                    {% else %}class="{{ cell.css_class }}"{% endif %}>
-                                    <ul class="form-list">
-                                        {% for label in cell.label %}
-                                            <li>
-                                                <span class="header-label">{{ label|safe }}</span>
-                                            </li>
-                                        {% endfor %}
-                                    </ul>
-                                </td>
-                            {% endif %}
-                        {% endif %}
-                    {% endif %}
-                {% endfor %}
-            </tr>
-        {% endfor %}
-    </table>
-</div>
\ No newline at end of file
diff --git a/dictionary/urls.py b/dictionary/urls.py
index 9327284..a34d046 100644
--- a/dictionary/urls.py
+++ b/dictionary/urls.py
@@ -25,7 +25,6 @@ urlpatterns += patterns(
     url(r'^ajax/odm-forms/$', 'odm_forms'),
     url(r'^ajax/lexeme-edit-form/$', 'lexeme_edit_form'),
     url(r'^ajax/update-lexeme/$', 'update_lexeme'),
-    url(r'^ajax/table-preview/$', 'table_preview'),
     url(r'^ajax/new-lip-row/$', 'new_lip_edit_row'),
     url(r'^ajax/new-cr-row/$', 'new_cross_reference_row'),
     url(r'^ajax/delete-lexeme/$', 'delete_lexeme'),
diff --git a/dictionary/util.py b/dictionary/util.py
index 4fb560e..8042d73 100644
--- a/dictionary/util.py
+++ b/dictionary/util.py
@@ -58,36 +58,3 @@ def check_query_params(request, query_params):
     reader = query_params.get('reader', True)
     if not request.user.is_authenticated() and not reader:
         raise AjaxError('access denied')
-
-
-def prepare_table(table):
-    for row in table:
-        for cell in row:
-            if type(cell) == dict and 'forms' in cell:
-                cell['forms'].sort()
-                seen_forms = []
-                unique_forms = []
-                form_patterns = {}
-                for form in cell['forms']:
-                    if form[1] not in seen_forms:
-                        seen_forms.append(form[1])
-                        unique_forms.append(form)
-                        form_patterns[form[1]] = set()
-                    form_patterns[form[1]].add(form[3])
-                cell['forms'] = [
-                    {
-                        'form': form,
-                        'qualifiers': qualifiers,
-                        'patterns': form_patterns[form],
-                    }
-                    for (key, form, qualifiers, pattern) in unique_forms]
-            elif type(cell) == dict and 'label' in cell:
-                seen_labels = []
-
-                def is_new(label):
-                    new = label not in seen_labels
-                    seen_labels.append(label)
-                    return new
-
-                cell['label'] = filter(is_new, cell['label'])
-    return table
\ No newline at end of file
diff --git a/management/templates/table_preview.html b/management/templates/table_preview.html
deleted file mode 100644
index 25288eb..0000000
--- a/management/templates/table_preview.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="scheme{{ color_scheme }}">
-    {% include 'inflection_table.html' %}
-</div>
\ No newline at end of file
diff --git a/settings.py b/settings.py
index a70e470..047bfc1 100644
--- a/settings.py
+++ b/settings.py
@@ -123,6 +123,7 @@ INSTALLED_APPS = (
     # aplikacje projektu
     'dictionary',
     'patterns',
+    'tables',
     'management',
     'export',
     'accounts',
diff --git a/tables/__init__.py b/tables/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tables/__init__.py
diff --git a/tables/management/__init__.py b/tables/management/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tables/management/__init__.py
diff --git a/tables/management/commands/__init__.py b/tables/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tables/management/commands/__init__.py
diff --git a/tables/management/commands/export_template.py b/tables/management/commands/export_template.py
new file mode 100644
index 0000000..b7de638
--- /dev/null
+++ b/tables/management/commands/export_template.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+from django.core.management.base import BaseCommand
+from common.util import json_encode, uniprint
+from dictionary.models import TableTemplate, Variant
+
+
+class Command(BaseCommand):
+    help = "Exports a table template to JSON."
+
+    def handle(self, name, variant, *args, **options):
+        tt = TableTemplate.objects.get(
+            name=name.decode('utf-8'), variant__id=variant)
+        uniprint(json_encode(export_template(tt)))
+
+
+def export_template(tt):
+    data = {'name': tt.name, 'variant': tt.variant.id}
+    if tt.variant.type == Variant.TYPE_TABLE:
+        data['table_cells'] = []
+        data['headers'] = []
+    else:
+        data['export_cells'] = []
+    table_cells = tt.table_cells.select_related(
+        'base_form_label__lexical_class').prefetch_related(
+        'pattern_types', 'attribute_values')
+    export_cells = tt.export_cells.select_related(
+        'base_form_label__lexical_class').prefetch_related(
+        'pattern_types', 'attribute_values')
+    headers = tt.headers.prefetch_related(
+        'pattern_types', 'attribute_values')
+    table_elements = [('table_cells', tc) for tc in table_cells]
+    table_elements += [('export_cells', ec) for ec in export_cells]
+    table_elements += [('headers', h) for h in headers]
+    for elem_type, elem in table_elements:
+        data_row = {
+            'pattern_types': list(
+                elem.pattern_types.values_list('symbol', 'lexical_class_id')),
+            'attr_vals': list(elem.attribute_values.values_list(
+                'value', 'attribute__name')),
+        }
+        if tt.takes_gender:
+            data_row['genders'] = list(
+                elem.genders.values_list('symbol', flat=True))
+        if elem_type in ('table_cells', 'headers'):
+            data_row.update({
+                'row': elem.row,
+                'col': elem.col,
+                'rowspan': elem.rowspan,
+                'colspan': elem.colspan,
+            })
+        if elem_type in ('table_cells', 'export_cells'):
+            data_row.update({
+                'bfl': (elem.base_form_label.symbol,
+                        elem.base_form_label.lexical_class.symbol),
+                'prefix': elem.prefix,
+                'suffix': elem.suffix,
+            })
+        if elem_type == 'table_cells':
+            data_row['index'] = elem.index
+        if elem_type == 'export_cells':
+            data_row['tag'] = elem.tag_template
+        if elem_type == 'headers':
+            data_row.update({
+                'label': elem.label,
+                'css_class': elem.css_class,
+            })
+        data[elem_type].append(data_row)
+    return data
diff --git a/tables/management/commands/export_templates.py b/tables/management/commands/export_templates.py
new file mode 100644
index 0000000..cf82ac4
--- /dev/null
+++ b/tables/management/commands/export_templates.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from django.core.management.base import BaseCommand
+
+from common.util import uniprint, json_encode
+from dictionary.models import TableTemplate
+from tables.management.commands.export_template import export_template
+
+
+class Command(BaseCommand):
+    help = "Exports all templates to JSON."
+
+    def handle(self, *args, **options):
+        uniprint(json_encode(export_templates()))
+
+
+def export_templates():
+    data = []
+    for tt in TableTemplate.objects.all():
+        data_row = export_template(tt)
+        data_row['parts_of_speech'] = list(
+            tt.parts_of_speech.values_list('symbol', flat=True))
+        data_row['pattern_types'] = list(
+            tt.pattern_types.values_list('symbol', 'lexical_class_id'))
+        data_row['attributes'] = list(
+            tt.attributes.values_list('name', flat=True))
+        data_row['attribute_values'] = list(
+            tt.attribute_values.values_list('value', 'attribute__name'))
+        data_row['cell_attributes'] = list(
+            tt.cell_attributes.values_list('name', flat=True))
+        data_row['takes_gender'] = tt.takes_gender
+        data.append(data_row)
+    return data
\ No newline at end of file
diff --git a/tables/management/commands/import_template.py b/tables/management/commands/import_template.py
new file mode 100644
index 0000000..4d1cd84
--- /dev/null
+++ b/tables/management/commands/import_template.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+import json
+from django.core.management.base import BaseCommand
+from dictionary.models import Variant, TableTemplate, TableCell, \
+    PatternType, LexemeAttributeValue, TableHeader, ExportCell, Gender
+from patterns.models import BaseFormLabel, PatternType
+
+
+class Command(BaseCommand):
+    help = "Imports a table template from JSON."
+
+    def handle(self, filename, *args, **options):
+        import_template(json.load(open(filename)))
+
+PATTERN_TYPES = dict(
+    ((pt.symbol, pt.lexical_class.symbol), pt)
+    for pt in PatternType.objects.all())
+
+GENDERS = dict((g.symbol, g) for g in Gender.objects.all())
+
+ATTR_VALS = dict(
+    ((av.value, av.attribute.name), av)
+    for av in LexemeAttributeValue.objects.all())
+
+BFLS = dict(
+    ((bfl.symbol, bfl.lexical_class.symbol), bfl)
+    for bfl in BaseFormLabel.objects.all())
+
+
+def get_bfl(data):
+    return BFLS[tuple(data['bfl'])]
+
+
+def import_template(data):
+    def add_restrictions(x, x_data):
+        x.pattern_types = (PATTERN_TYPES[tuple(pt_data)]
+                           for pt_data in x_data['pattern_types'])
+        if 'genders' in x_data:
+            x.genders = (GENDERS[gender] for gender in x_data['genders'])
+        x.attribute_values = (
+            ATTR_VALS[tuple(av_data)] for av_data in x_data['attr_vals'])
+
+    variant = Variant.objects.get(id=data['variant'])
+    tt, created = TableTemplate.objects.get_or_create(
+        variant=variant, name=data['name'])
+    if not created:
+        tt.table_cells.all().delete()
+        tt.headers.all().delete()
+        tt.export_cells.all().delete()
+    for tc_data in data.get('table_cells', []):
+        tc = TableCell(
+            table_template=tt,
+            row=tc_data['row'], col=tc_data['col'], rowspan=tc_data['rowspan'],
+            colspan=tc_data['colspan'], index=tc_data['index'],
+            base_form_label=get_bfl(tc_data), prefix=tc_data['prefix'],
+            suffix=tc_data['suffix'])
+        tc.save()
+        add_restrictions(tc, tc_data)
+    for h_data in data.get('headers', []):
+        th = TableHeader(
+            table_template=tt,
+            row=h_data['row'], col=h_data['col'], rowspan=h_data['rowspan'],
+            colspan=h_data['colspan'], label=h_data['label'],
+            css_class=h_data['css_class'])
+        th.save()
+        add_restrictions(th, h_data)
+    for ec_data in data.get('export_cells', []):
+        ec = ExportCell(
+            table_template=tt,
+            base_form_label=get_bfl(ec_data), prefix=ec_data['prefix'],
+            suffix=ec_data['suffix'], tag_template=ec_data['tag'])
+        ec.save()
+        add_restrictions(ec, ec_data)
+    return tt
\ No newline at end of file
diff --git a/tables/management/commands/import_templates.py b/tables/management/commands/import_templates.py
new file mode 100644
index 0000000..6be9c5c
--- /dev/null
+++ b/tables/management/commands/import_templates.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+import json
+
+from django.core.management.base import BaseCommand
+
+from dictionary.models import TableTemplate, LexemeAttribute, \
+    LexemeAttributeValue
+from patterns.models import PatternType
+from tables.management.commands.import_template import import_template
+
+
+class Command(BaseCommand):
+    help = "Imports templated from JSON."
+
+    def handle(self, filename, *args, **options):
+        import_templates(json.load(open(filename)))
+
+PATTERN_TYPES = dict(
+    ((pt.symbol, pt.lexical_class.symbol), pt)
+    for pt in PatternType.objects.all())
+
+ATTRS = dict((attr.name, attr) for attr in LexemeAttribute.objects.all())
+
+ATTR_VALS = dict(
+    ((av.value, av.attribute.name), av)
+    for av in LexemeAttributeValue.objects.all())
+
+
+def import_templates(data):
+    TableTemplate.objects.all().delete()
+    for tt_data in data:
+        tt = import_template(tt_data)
+        tt.parts_of_speech = tt_data['parts_of_speech']
+        tt.pattern_types = (PATTERN_TYPES[tuple(pt_data)]
+                            for pt_data in tt_data['pattern_types'])
+        tt.attributes = (ATTRS[attr_name]
+                         for attr_name in tt_data['attributes'])
+        for av_data in tt_data['attribute_values']:
+            tt.attribute_values.add(ATTR_VALS[tuple(av_data)])
+        for attr_name in tt_data['cell_attributes']:
+            tt.cell_attributes.add(ATTRS[attr_name])
+        tt.takes_gender = tt_data['takes_gender']
+        tt.save()
\ No newline at end of file
diff --git a/tables/migrations/__init__.py b/tables/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tables/migrations/__init__.py
diff --git a/tables/models.py b/tables/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/tables/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/tables/templates/inflection_table.html b/tables/templates/inflection_table.html
new file mode 100644
index 0000000..d906672
--- /dev/null
+++ b/tables/templates/inflection_table.html
@@ -0,0 +1,60 @@
+<div class="scheme{{ color_scheme }}">
+    <table class="inflection-table">
+        {% if tables and tables|length > 1 %}
+            <caption>
+                {{ gender.symbol }}:
+                {% if pronunciations %}
+                    <span class="lip-pronunciation">[{{ pronunciations|join:"/"|safe }}]{% if gender_qualifiers %};{% endif %}</span>
+                {% endif %}
+                <span class="qualifiers">{{ gender_qualifiers|join:" " }}</span>
+                {% include "pattern_list.html" %}
+            </caption>
+        {% endif %}
+        {% for row in table %}
+            <tr>
+                {% for cell in row %}
+                    {% if cell.type != "span" %}
+                        {% if cell.type == "empty" %}
+                            <td class="empty"></td>
+                        {% else %}
+                            {% if cell.type == "forms" %}
+                                <td rowspan="{{ cell.rowspan }}"
+                                    colspan="{{ cell.colspan }}"
+                                    {% if cell.colspan > 1 %}class="data-c"{% endif %}>
+                                    <ul class="form-list">
+                                        {% for entry in cell.forms %}
+                                            <li>
+                                                <span class="form {% for p in entry.patterns %}p{{ p.id }} {% endfor %}">
+                                                    {{ entry.form|safe }}
+                                                </span>
+                                                <span class="qualifiers">
+                                                  {% for q in entry.qualifiers %}
+                                                      {{ q.label }}
+                                                  {% endfor %}
+                                                </span>
+                                            </li>
+                                        {% endfor %}
+                                    </ul>
+                                </td>
+                            {% else %}
+                                <td rowspan="{{ cell.rowspan }}"
+                                    colspan="{{ cell.colspan }}"
+                                    {% if cell.colspan > 1 and cell.css_class == 'data' %}
+                                    class="data-c"
+                                    {% else %}class="{{ cell.css_class }}"{% endif %}>
+                                    <ul class="form-list">
+                                        {% for label in cell.label %}
+                                            <li>
+                                                <span class="header-label">{{ label|safe }}</span>
+                                            </li>
+                                        {% endfor %}
+                                    </ul>
+                                </td>
+                            {% endif %}
+                        {% endif %}
+                    {% endif %}
+                {% endfor %}
+            </tr>
+        {% endfor %}
+    </table>
+</div>
\ No newline at end of file
diff --git a/tables/templates/table_preview.html b/tables/templates/table_preview.html
new file mode 100644
index 0000000..25288eb
--- /dev/null
+++ b/tables/templates/table_preview.html
@@ -0,0 +1,3 @@
+<div class="scheme{{ color_scheme }}">
+    {% include 'inflection_table.html' %}
+</div>
\ No newline at end of file
diff --git a/tables/tests.py b/tables/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/tables/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/tables/urls.py b/tables/urls.py
new file mode 100644
index 0000000..d506bdf
--- /dev/null
+++ b/tables/urls.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+
+from django.conf.urls import patterns
+from common.util import url
+
+
+urlpatterns = patterns(
+    'tables.views',
+    url(r'^ajax/table-preview/$', 'table_preview'),
+)
\ No newline at end of file
diff --git a/tables/util.py b/tables/util.py
new file mode 100644
index 0000000..9804dca
--- /dev/null
+++ b/tables/util.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+
+def prepare_table(table):
+    for row in table:
+        for cell in row:
+            if type(cell) == dict and 'forms' in cell:
+                cell['forms'].sort()
+                seen_forms = []
+                unique_forms = []
+                form_patterns = {}
+                for form in cell['forms']:
+                    if form[1] not in seen_forms:
+                        seen_forms.append(form[1])
+                        unique_forms.append(form)
+                        form_patterns[form[1]] = set()
+                    form_patterns[form[1]].add(form[3])
+                cell['forms'] = [
+                    {
+                        'form': form,
+                        'qualifiers': qualifiers,
+                        'patterns': form_patterns[form],
+                    }
+                    for (key, form, qualifiers, pattern) in unique_forms]
+            elif type(cell) == dict and 'label' in cell:
+                seen_labels = []
+
+                def is_new(label):
+                    new = label not in seen_labels
+                    seen_labels.append(label)
+                    return new
+
+                cell['label'] = filter(is_new, cell['label'])
+    return table
diff --git a/tables/views.py b/tables/views.py
new file mode 100644
index 0000000..81671bf
--- /dev/null
+++ b/tables/views.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+from django.shortcuts import render
+
+from common.decorators import render_ajax, AjaxError
+from dictionary.models import Lexeme, PartOfSpeech, Gender, \
+    LexemeAttributeValue, LexemeInflectionPattern, Qualifier
+from tables.util import prepare_table
+from patterns.models import Pattern
+
+
+@render_ajax(template='table_preview.html', method='get')
+def table_preview(request, lexeme_id, lip_id, pattern, attr_data=None,
+                  gender=None, entry=None, pos=None):
+    lexeme = Lexeme.all_objects.get(pk=lexeme_id)
+    if not lexeme.perm(request.user, 'view'):
+        raise AjaxError('access denied')
+    if pos is not None:
+        part_of_speech = PartOfSpeech.objects.get(symbol=pos)
+    else:
+        part_of_speech = lexeme.part_of_speech
+    try:
+        if entry is not None:
+            lexeme.entry = entry
+        pattern = Pattern.objects.get(name=pattern)
+        gender = Gender.objects.get(id=gender) if gender else None
+        if attr_data:
+            attr_vals = LexemeAttributeValue.objects.filter(
+                id__in=attr_data)
+        else:
+            attr_vals = None
+        if lip_id.startswith('lip_add'):
+            lip = LexemeInflectionPattern(lexeme=lexeme, index=0)
+        else:
+            lip = LexemeInflectionPattern.objects.get(pk=int(lip_id[3:]))
+        lip.pattern = pattern
+        lip.gender = gender
+        lip.root = lip.get_root()
+        qualifiers = Qualifier.visible_qualifiers(request.user)
+        table = lip.inflection_table(
+            '0', separated=True, qualifiers=qualifiers, edit_view=True,
+            attr_vals=attr_vals, pos=part_of_speech)
+        prepare_table(table)
+    except Pattern.DoesNotExist:
+        table = None
+    return {'table': table, 'color_scheme': part_of_speech.color_scheme}
\ No newline at end of file
diff --git a/urls.py b/urls.py
index 8e4ac83..8e07901 100644
--- a/urls.py
+++ b/urls.py
@@ -36,4 +36,5 @@ urlpatterns = patterns(
     (r'^paginer/', include('paginer.urls')),
     (r'^historia/', include('history.urls')),
     (r'^eksport/', include('export.urls')),
+    (r'^tables/', include('tables.urls')),
 )
--
libgit2 0.22.2