def test_extract_allowed():
    assert extract_allowed({
        'test_1': 'test_1',
        'test_2': 'test_2'
    }, ['test_1']) == {
        'test_1': 'test_1'
    }

    assert extract_allowed({}, ['test_1']) == {}
    assert extract_allowed({'test_1': 'test'}, ['test_2']) == {}
    assert extract_allowed({'test_1': 'test'}, []) == {}
Example #2
0
def test_extract_allowed():
    assert extract_allowed({
        "test_1": "test_1",
        "test_2": "test_2"
    }, ["test_1"]) == {
        "test_1": "test_1"
    }

    assert extract_allowed({}, ["test_1"]) == {}
    assert extract_allowed({"test_1": "test"}, ["test_2"]) == {}
    assert extract_allowed({"test_1": "test"}, []) == {}
Example #3
0
    def create_view(self, user, table, type_name, **kwargs):
        """
        Creates a new view based on the provided type.

        :param user: The user on whose behalf the view is created.
        :type user: User
        :param table: The table that the view instance belongs to.
        :type table: Table
        :param type_name: The type name of the view.
        :type type_name: str
        :param kwargs: The fields that need to be set upon creation.
        :type kwargs: object
        :return: The created view instance.
        :rtype: View
        """

        group = table.database.group
        group.has_user(user, raise_error=True)

        # Figure out which model to use for the given view type.
        view_type = view_type_registry.get(type_name)
        model_class = view_type.model_class
        allowed_fields = ['name', 'filter_type', 'filters_disabled'
                          ] + view_type.allowed_fields
        view_values = extract_allowed(kwargs, allowed_fields)
        last_order = model_class.get_last_order(table)

        instance = model_class.objects.create(table=table,
                                              order=last_order,
                                              **view_values)

        view_created.send(self, view=instance, user=user, type_name=type_name)

        return instance
Example #4
0
    def create_view(self, user, table, type_name, **kwargs):
        """
        Creates a new view based on the provided type.

        :param user: The user on whose behalf the view is created.
        :type user: User
        :param table: The table that the view instance belongs to.
        :type table: Table
        :param type_name: The type name of the view.
        :type type_name: str
        :param kwargs: The fields that need to be set upon creation.
        :type kwargs: object
        :raises UserNotInGroupError: When the user does not belong to the related group.
        :return: The created view instance.
        :rtype: View
        """

        group = table.database.group
        if not group.has_user(user):
            raise UserNotInGroupError(user, group)

        # Figure out which model to use for the given view type.
        view_type = view_type_registry.get(type_name)
        model_class = view_type.model_class
        allowed_fields = ['name'] + view_type.allowed_fields
        view_values = extract_allowed(kwargs, allowed_fields)
        last_order = model_class.get_last_order(table)

        instance = model_class.objects.create(table=table,
                                              order=last_order,
                                              **view_values)

        return instance
Example #5
0
    def create_field(self, user, table, type_name, primary=False, **kwargs):
        """
        Creates a new field with the given type for a table.

        :param user: The user on whose behalf the field is created.
        :type user: User
        :param table: The table that the field belongs to.
        :type table: Table
        :param type_name: The type name of the field. Available types can be found in
            the field_type_registry.
        :type type_name: str
        :param primary: Every table needs at least a primary field which cannot be
            deleted and is a representation of the whole row.
        :type primary: bool
        :param kwargs: The field values that need to be set upon creation.
        :type kwargs: object
        :raises UserNotInGroupError: When the user does not belong to the related group.
        :raises PrimaryFieldAlreadyExists: When we try to create a primary field,
            but one already exists.
        :return: The created field instance.
        :rtype: Field
        """

        group = table.database.group
        if not group.has_user(user):
            raise UserNotInGroupError(user, group)

        # Because only one primary field per table can exist and we have to check if one
        # already exists. If so the field cannot be created and an exception is raised.
        if primary and Field.objects.filter(table=table,
                                            primary=True).exists():
            raise PrimaryFieldAlreadyExists(
                f'A primary field already exists for the '
                f'table {table}.')

        # Figure out which model to use and which field types are allowed for the given
        # field type.
        field_type = field_type_registry.get(type_name)
        model_class = field_type.model_class
        allowed_fields = ['name'] + field_type.allowed_fields
        field_values = extract_allowed(kwargs, allowed_fields)
        last_order = model_class.get_last_order(table)

        instance = model_class.objects.create(table=table,
                                              order=last_order,
                                              primary=primary,
                                              **field_values)

        # Add the field to the table schema.
        connection = connections[settings.USER_TABLE_DATABASE]
        with connection.schema_editor() as schema_editor:
            to_model = table.get_model(field_ids=[], fields=[instance])
            model_field = to_model._meta.get_field(instance.db_column)
            schema_editor.add_field(to_model, model_field)

        return instance
Example #6
0
    def create_table(self, user, database, fill_initial=False, **kwargs):
        """
        Creates a new table and a primary text field.

        :param user: The user on whose behalf the table is created.
        :type user: User
        :param database: The database that the table instance belongs to.
        :type database: Database
        :param fill_initial: Indicates whether an initial view, some fields and
            some rows should be added.
        :type fill_initial: bool
        :param kwargs: The fields that need to be set upon creation.
        :type kwargs: object
        :raises UserNotInGroupError: When the user does not belong to the related group.
        :return: The created table instance.
        :rtype: Table
        """

        if not database.group.has_user(user):
            raise UserNotInGroupError(user, database.group)

        table_values = extract_allowed(kwargs, ['name'])
        last_order = Table.get_last_order(database)
        table = Table.objects.create(database=database,
                                     order=last_order,
                                     **table_values)

        # Create a primary text field for the table.
        TextField.objects.create(table=table,
                                 order=0,
                                 primary=True,
                                 name='Name')

        # Create the table schema in the database database.
        connection = connections[settings.USER_TABLE_DATABASE]
        with connection.schema_editor() as schema_editor:
            model = table.get_model()
            schema_editor.create_model(model)

        if fill_initial:
            self.fill_initial_table_data(user, table)

        return table
Example #7
0
    def update_field(self, user, field, new_type_name=None, **kwargs):
        """
        Updates the values of the given field, if provided it is also possible to change
        the type.

        :param user: The user on whose behalf the table is updated.
        :type user: User
        :param field: The field instance that needs to be updated.
        :type field: Field
        :param new_type_name: If the type needs to be changed it can be provided here.
        :type new_type_name: str
        :param kwargs: The field values that need to be updated
        :type kwargs: object
        :raises ValueError: When the provided field is not an instance of Field.
        :raises CannotChangeFieldType: When the database server responds with an
            error while trying to change the field type. This should rarely happen
            because of the lenient schema editor, which replaces the value with null
            if it ould not be converted.
        :return: The updated field instance.
        :rtype: Field
        """

        if not isinstance(field, Field):
            raise ValueError('The field is not an instance of Field.')

        group = field.table.database.group
        group.has_user(user, raise_error=True)

        old_field = deepcopy(field)
        field_type = field_type_registry.get_by_model(field)
        old_field_type = field_type
        from_model = field.table.get_model(field_ids=[], fields=[field])
        from_field_type = field_type.type

        # If the provided field type does not match with the current one we need to
        # migrate the field to the new type. Because the type has changed we also need
        # to remove all view filters.
        if new_type_name and field_type.type != new_type_name:
            field_type = field_type_registry.get(new_type_name)

            if field.primary and not field_type.can_be_primary_field:
                raise IncompatiblePrimaryFieldTypeError(new_type_name)

            new_model_class = field_type.model_class
            field.change_polymorphic_type_to(new_model_class)

            # If the field type changes it could be that some dependencies,
            # like filters or sortings need to be changed.
            ViewHandler().field_type_changed(field)

        allowed_fields = ['name'] + field_type.allowed_fields
        field_values = extract_allowed(kwargs, allowed_fields)

        field_values = field_type.prepare_values(field_values, user)
        before = field_type.before_update(old_field, field_values, user)

        field = set_allowed_attrs(field_values, allowed_fields, field)
        field.save()

        connection = connections[settings.USER_TABLE_DATABASE]

        # If no converter is found we are going to convert to field using the
        # lenient schema editor which will alter the field's type and set the data
        # value to null if it can't be converted.
        to_model = field.table.get_model(field_ids=[], fields=[field])
        from_model_field = from_model._meta.get_field(field.db_column)
        to_model_field = to_model._meta.get_field(field.db_column)

        # Before a field is updated we are going to call the before_schema_change
        # method of the old field because some cleanup of related instances might
        # need to happen.
        old_field_type.before_schema_change(old_field, field, from_model,
                                            to_model, from_model_field,
                                            to_model_field, user)

        # Try to find a data converter that can be applied.
        converter = field_converter_registry.find_applicable_converter(
            from_model, old_field, field)

        if converter:
            # If a field data converter is found we are going to use that one to alter
            # the field and maybe do some data conversion.
            converter.alter_field(old_field, field, from_model, to_model,
                                  from_model_field, to_model_field, user,
                                  connection)
        else:
            # If no field converter is found we are going to alter the field using the
            # the lenient schema editor.
            with lenient_schema_editor(
                    connection,
                    old_field_type.get_alter_column_prepare_old_value(
                        connection, old_field, field),
                    field_type.get_alter_column_prepare_new_value(
                        connection, old_field, field)) as schema_editor:
                try:
                    schema_editor.alter_field(from_model, from_model_field,
                                              to_model_field)
                except (ProgrammingError, DataError) as e:
                    # If something is going wrong while changing the schema we will
                    # just raise a specific exception. In the future we want to have
                    # some sort of converter abstraction where the values of certain
                    # types can be converted to another value.
                    logger.error(str(e))
                    message = f'Could not alter field when changing field type ' \
                              f'{from_field_type} to {new_type_name}.'
                    raise CannotChangeFieldType(message)

        from_model_field_type = from_model_field.db_parameters(
            connection)['type']
        to_model_field_type = to_model_field.db_parameters(connection)['type']
        altered_column = from_model_field_type != to_model_field_type

        # If the new field doesn't support select options we can delete those
        # relations.
        if (old_field_type.can_have_select_options
                and not field_type.can_have_select_options):
            old_field.select_options.all().delete()

        field_type.after_update(old_field, field, from_model, to_model, user,
                                connection, altered_column, before)

        field_updated.send(self, field=field, user=user)

        return field
Example #8
0
    def create_field(self,
                     user,
                     table,
                     type_name,
                     primary=False,
                     do_schema_change=True,
                     **kwargs):
        """
        Creates a new field with the given type for a table.

        :param user: The user on whose behalf the field is created.
        :type user: User
        :param table: The table that the field belongs to.
        :type table: Table
        :param type_name: The type name of the field. Available types can be found in
            the field_type_registry.
        :type type_name: str
        :param primary: Every table needs at least a primary field which cannot be
            deleted and is a representation of the whole row.
        :type primary: bool
        :param do_schema_change: Indicates whether or not he actual database schema
            change has be made.
        :type do_schema_change: bool
        :param kwargs: The field values that need to be set upon creation.
        :type kwargs: object
        :raises PrimaryFieldAlreadyExists: When we try to create a primary field,
            but one already exists.
        :raises MaxFieldLimitExceeded: When we try to create a field,
            but exceeds the field limit.
        :return: The created field instance.
        :rtype: Field
        """

        group = table.database.group
        group.has_user(user, raise_error=True)

        # Because only one primary field per table can exist and we have to check if one
        # already exists. If so the field cannot be created and an exception is raised.
        if primary and Field.objects.filter(table=table,
                                            primary=True).exists():
            raise PrimaryFieldAlreadyExists(
                f"A primary field already exists for the "
                f"table {table}.")

        # Figure out which model to use and which field types are allowed for the given
        # field type.
        field_type = field_type_registry.get(type_name)
        model_class = field_type.model_class
        allowed_fields = ["name"] + field_type.allowed_fields
        field_values = extract_allowed(kwargs, allowed_fields)
        last_order = model_class.get_last_order(table)

        num_fields = table.field_set.count()
        if (num_fields + 1) > settings.MAX_FIELD_LIMIT:
            raise MaxFieldLimitExceeded(
                f"Fields count exceeds the limit of {settings.MAX_FIELD_LIMIT}"
            )

        field_values = field_type.prepare_values(field_values, user)
        before = field_type.before_create(table, primary, field_values,
                                          last_order, user)

        instance = model_class.objects.create(table=table,
                                              order=last_order,
                                              primary=primary,
                                              **field_values)

        # Add the field to the table schema.
        connection = connections[settings.USER_TABLE_DATABASE]
        with connection.schema_editor() as schema_editor:
            to_model = table.get_model(field_ids=[], fields=[instance])
            model_field = to_model._meta.get_field(instance.db_column)

            if do_schema_change:
                schema_editor.add_field(to_model, model_field)

        field_type.after_create(instance, to_model, user, connection, before)

        field_created.send(self,
                           field=instance,
                           user=user,
                           type_name=type_name)

        return instance
Example #9
0
    def create_table(self, user, database, fill_example=False, data=None,
                     first_row_header=True, **kwargs):
        """
        Creates a new table and a primary text field.

        :param user: The user on whose behalf the table is created.
        :type user: User
        :param database: The database that the table instance belongs to.
        :type database: Database
        :param fill_example: Indicates whether an initial view, some fields and
            some rows should be added. Works only if no data is provided.
        :type fill_example: bool
        :param data: A list containing all the rows that need to be inserted is
            expected. All the values of the row are going to be converted to a string
            and will be inserted in the database.
        :type: initial_data: None or list[list[str]
        :param first_row_header: Indicates if the first row are the fields. The names
            of these rows are going to be used as fields.
        :type first_row_header: bool
        :param kwargs: The fields that need to be set upon creation.
        :type kwargs: object
        :raises UserNotInGroupError: When the user does not belong to the related group.
        :return: The created table instance.
        :rtype: Table
        """

        if not database.group.has_user(user):
            raise UserNotInGroupError(user, database.group)

        if data is not None:
            fields, data = self.normalize_initial_table_data(data, first_row_header)

        table_values = extract_allowed(kwargs, ['name'])
        last_order = Table.get_last_order(database)
        table = Table.objects.create(database=database, order=last_order,
                                     **table_values)

        if data is not None:
            # If the initial data has been provided we will create those fields before
            # creating the model so that we the whole table schema is created right
            # away.
            for index, name in enumerate(fields):
                fields[index] = TextField.objects.create(
                    table=table,
                    order=index,
                    primary=index == 0,
                    name=name
                )

        else:
            # If no initial data is provided we want to create a primary text field for
            # the table.
            TextField.objects.create(table=table, order=0, primary=True, name='Name')

        # Create the table schema in the database database.
        connection = connections[settings.USER_TABLE_DATABASE]
        with connection.schema_editor() as schema_editor:
            model = table.get_model()
            schema_editor.create_model(model)

        if data is not None:
            self.fill_initial_table_data(user, table, fields, data, model)
        elif fill_example:
            self.fill_example_table_data(user, table)

        return table