models.py 11.4 KB
# -*- coding: utf-8 -*-
from itertools import izip

from django.db.models import Model, CharField, TextField, ForeignKey, \
    ManyToManyField, BooleanField, IntegerField

from dictionary.models import PartOfSpeech, LexemeAttribute, \
    LexemeAttributeValue, Gender
from patterns.models import PatternType, BaseFormLabel
from tables.util import prepare_table


class Variant(Model):
    TYPE_TABLE = 'table'
    TYPE_EXPORT = 'export'
    TYPE_CHOICES = (
        (TYPE_TABLE, 'tabelka'),
        (TYPE_EXPORT, 'eksport'),
    )

    id = CharField(max_length=32, primary_key=True, db_column='wariant')
    type = CharField(max_length=10, choices=TYPE_CHOICES)

    def __unicode__(self):
        return self.id

    class Meta:
        db_table = 'warianty'


class TableTemplate(Model):
    name = TextField()
    variant = ForeignKey(Variant)
    parts_of_speech = ManyToManyField(PartOfSpeech)
    pattern_types = ManyToManyField(PatternType)
    attributes = ManyToManyField(LexemeAttribute)
    attribute_values = ManyToManyField(LexemeAttributeValue)
    cell_attributes = ManyToManyField(LexemeAttribute, related_name='templates')
    takes_gender = BooleanField(default=False)

    def filter_cells_attr(self, cells, attr_vals):
        for attr in self.cell_attributes.all():
            attr_val = [a for a in attr_vals if a.attribute == attr]
            if len(attr_val) != 1:
                return []
            cells = cells.filter(attribute_values=attr_val[0])
        return cells

    def filter_cells(self, pattern_type, gender, attr_vals):
        if self.variant.type == Variant.TYPE_TABLE:
            cells = self.table_cells.filter(
                pattern_types=pattern_type).select_related('base_form_label')
        else:
            cells = self.export_cells.filter(
                pattern_types=pattern_type).select_related('base_form_label')
        if self.takes_gender and gender:
            cells = cells.filter(genders=gender)
        if attr_vals:
            cells = self.filter_cells_attr(cells, attr_vals)
        return cells

    def filter_table_headers(self, pattern_type, gender, attr_vals):
        headers = self.headers.filter(pattern_types=pattern_type)
        if self.takes_gender and gender:
            headers = headers.filter(genders=gender)
        if attr_vals:
            headers = self.filter_cells_attr(headers, attr_vals)
        return headers

    def render_with_pattern(self, pattern, *args, **kwargs):
        base_endings = pattern.base_endings()
        return self.render(
            pattern.type, *args, pattern=pattern, base_endings=base_endings,
            **kwargs)

    def render_with_pattern_type(self, pattern_type, *args, **kwargs):
        base_endings = pattern_type.dummy_base_endings()
        return prepare_table(self.render(
            pattern_type, *args, base_endings=base_endings, numbers=True,
            **kwargs))

    def render(self, pattern_type, gender, attr_vals, separated=False,
               numbers=False, pattern=None, **forms_kwargs):
        table_cells = self.filter_cells(pattern_type, gender, attr_vals)
        headers = self.filter_table_headers(pattern_type, gender, attr_vals)
        if table_cells is None or headers is None:
            return []
        rows = set()
        last_col = 0
        for table_cell in table_cells:
            rows.add(table_cell.row)
            col = table_cell.col + table_cell.colspan - 1
            if col > last_col:
                last_col = col
        for header in headers:
            rows.add(header.row)
            col = header.col + header.colspan - 1
            if col > last_col:
                last_col = col
        if numbers:
            last_col += 1
        table = [[{'type': 'empty'} for i in xrange(last_col)] for row in rows]
        rows = sorted(rows)
        if numbers:
            for row, table_row in izip(rows, table):
                table_row[0] = {
                    'type': 'header',
                    'label': [row],
                    'css_class': 'blank',
                    'rowspan': 1,
                    'colspan': 1}
        # słownik: nr rzędu w bazie -> rzeczywisty numer rzędu
        row_translate = dict((r, i) for i, r in enumerate(rows))
        for tc in table_cells:
            if numbers:
                x = tc.col
            else:
                x = tc.col - 1
            y = row_translate[tc.row]
            table_cell = table[y][x]
            assert table_cell['type'] != 'span'
            separator = u'·' if separated else u''
            forms = [f + (pattern,)
                     for f in tc.forms(separator=separator, **forms_kwargs)]
            if not forms:
                continue
            if table_cell['type'] == 'empty':
                table[y][x] = {
                    'type': 'forms',
                    'forms': forms,
                    'rowspan': tc.rowspan,
                    'colspan': tc.colspan,
                }
                for i in xrange(tc.colspan):
                    for j in xrange(tc.rowspan):
                        if (i, j) != (0, 0):
                            assert table[y + j][x + i]['type'] == 'empty'
                            table[y + j][x + i]['type'] = 'span'
            else:
                assert tc.rowspan == table_cell['rowspan']
                assert tc.colspan == table_cell['colspan']
                table_cell['forms'] += forms
        for header in headers:
            if numbers:
                x = header.col
            else:
                x = header.col - 1
            y = row_translate[header.row]
            assert table[y][x]['type'] == 'empty'
            table[y][x] = {
                'type': 'label',
                'label': [header.label],
                'css_class': header.css_class,
                'rowspan': header.rowspan,
                'colspan': header.colspan,
            }
            for i in xrange(header.colspan):
                for j in xrange(header.rowspan):
                    if (i, j) != (0, 0):
                        assert table[y + j][x + i]['type'] == 'empty'
                        table[y + j][x + i]['type'] = 'span'
        if numbers:
            first_row = [{
                'type': 'header',
                'label': [col if col != 0 else ''],
                'css_class': 'blank',
                'rowspan': 1,
                'colspan': 1} for col in xrange(last_col)]
            table = [first_row] + table
        return [row for row in table
                if not all(cell['type'] == 'empty' for cell in row)]

    def __unicode__(self):
        return self.name


def combine_qualifiers(inflection_qualifiers, ending_qualifiers):
    if not inflection_qualifiers:
        return ending_qualifiers
    qualifiers = set()
    for q in list(inflection_qualifiers) + list(ending_qualifiers):
        if q.exclusion_class:
            excluded = set(q.exclusion_class.qualifier_set.all())
            qualifiers -= excluded
        qualifiers.add(q)
    return qualifiers


class Cell(Model):
    base_form_label = ForeignKey(BaseFormLabel)
    prefix = CharField(max_length=20, blank=True)
    suffix = CharField(max_length=20, blank=True)

    def get_index(self):
        return None

    def get_qualifier(self):
        return ''

    def get_marked_attribute_values(self):
        return set()

    def forms(self, base_endings=None, separator=u'', root=u'',
              inflection_qualifiers=None, inflection_index=0, qualifiers=None,
              edit_view=False, span=False, depr=None, cell_qualifier=False):
        if qualifiers:
            qualifiers_set = set(qualifiers)

        def filter_quals(quals):
            if not qualifiers:
                return set(quals)
            else:
                return set(quals) & qualifiers_set

        if inflection_qualifiers and not edit_view:
            # l_qual = filter_quals(lexeme_qualifiers)
            inflection_qual = filter_quals(inflection_qualifiers)
        else:
            inflection_qual = set()
        endings = base_endings[self.base_form_label]
        if span:
            form_template = (
                '<span class="root">%s</span>'
                '%s<span class="ending">%s</span>')
        else:
            form_template = '%s%s%s'
        if self.prefix and span:
            form_template = '<span class="prefix">%s</span>' + form_template
        else:
            form_template = '%s' + form_template
        if self.suffix and span:
            form_template += '<span class="suffix">%s</span>'
        else:
            form_template += '%s'
        if depr and depr in self.get_marked_attribute_values():
            form_template = (
                '<span class="marked-form" title="%s">%s</span>'
                % (depr.display_value, form_template))
        if cell_qualifier and self.get_qualifier():
            form_template += (
                ' <span class="cell-qualifier">%s</span>'
                % self.get_qualifier())
        forms = [
            (
                (self.get_index(), inflection_index, ending.index),
                (form_template % (
                 self.prefix, root, separator, ending.string, self.suffix)),
                combine_qualifiers(
                    inflection_qual, filter_quals(ending.qualifiers.all())),
            )
            for ending in endings
        ]
        return forms

    class Meta:
        abstract = True


class TableCell(Cell):
    table_template = ForeignKey('tables.TableTemplate', related_name='table_cells')
    pattern_types = ManyToManyField(PatternType)
    genders = ManyToManyField(Gender)
    attribute_values = ManyToManyField(
        LexemeAttributeValue, related_name='table_cells')
    marked_attribute_values = ManyToManyField(
        LexemeAttributeValue, related_name='marked_cells')
    qualifier = CharField(max_length=128, blank=True)
    row = IntegerField()
    col = IntegerField()
    rowspan = IntegerField()
    colspan = IntegerField()
    index = IntegerField()

    def get_index(self):
        return self.index

    def get_qualifier(self):
        return self.qualifier

    def get_marked_attribute_values(self):
        return set(self.marked_attribute_values.all())

    def __unicode__(self):
        return '%s:%s %s-%s-%s (%s, %s)' % (
            self.table_template.variant_id, self.table_template.name,
            self.prefix, self.base_form_label.symbol, self.suffix,
            self.row, self.col)


class ExportCell(Cell):
    table_template = ForeignKey('tables.TableTemplate', related_name='export_cells')
    pattern_types = ManyToManyField(PatternType)
    genders = ManyToManyField(Gender)
    attribute_values = ManyToManyField(LexemeAttributeValue)
    tag_template = TextField()

    def __unicode__(self):
        return '%s:%s %s-%s-%s (%s)' % (
            self.table_template.variant.id, self.table_template.name,
            self.prefix, self.base_form_label.symbol, self.suffix,
            self.tag_template)


class TableHeader(Model):
    table_template = ForeignKey('tables.TableTemplate', related_name='headers')
    pattern_types = ManyToManyField(PatternType)
    genders = ManyToManyField(Gender)
    attribute_values = ManyToManyField(LexemeAttributeValue)
    row = IntegerField()
    col = IntegerField()
    rowspan = IntegerField()
    colspan = IntegerField()
    label = CharField(max_length=64, blank=True, db_column='nagl')
    css_class = CharField(max_length=8, db_column='styl')

    def __unicode__(self):
        return u'%s:%s %s (%s, %s)' % (
            self.table_template.variant_id, self.table_template.name,
            self.label, self.row, self.col)