Exemple #1
0
def verify_related_name_dont_duplicate(
    related_name: str, model_field: "ForeignKeyField"
) -> None:
    """
    Verifies whether the used related_name (regardless of the fact if user defined or
    auto generated) is already used on related model, but is connected with other model
    than the one that we connect right now.

    :raises ModelDefinitionError: if name is already used but lead to different related
    model
    :param related_name:
    :type related_name:
    :param model_field: original relation ForeignKey field
    :type model_field: relation Field
    :return: None
    :rtype: None
    """
    fk_field = model_field.to.Meta.model_fields.get(related_name)
    if not fk_field:  # pragma: no cover
        return
    if fk_field.to != model_field.owner and fk_field.to.Meta != model_field.owner.Meta:
        raise ormar.ModelDefinitionError(
            f"Relation with related_name "
            f"'{related_name}' "
            f"leading to model "
            f"{model_field.to.get_name(lower=False)} "
            f"cannot be used on model "
            f"{model_field.owner.get_name(lower=False)} "
            f"because it's already used by model "
            f"{fk_field.to.get_name(lower=False)}"
        )
Exemple #2
0
def create_and_append_m2m_fk(model: Type["Model"],
                             model_field: Type["ManyToManyField"],
                             field_name: str) -> None:
    """
    Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.

    Newly created field is added to m2m relation through model Meta columns and table.

    :param field_name: name of the column to create
    :type field_name: str
    :param model: Model class to which FK should be created
    :type model: Model class
    :param model_field: field with ManyToMany relation
    :type model_field: ManyToManyField field
    """
    pk_alias = model.get_column_alias(model.Meta.pkname)
    pk_column = next(
        (col for col in model.Meta.columns if col.name == pk_alias), None)
    if pk_column is None:  # pragma: no cover
        raise ormar.ModelDefinitionError(
            "ManyToMany relation cannot lead to field without pk")
    column = sqlalchemy.Column(
        field_name,
        pk_column.type,
        sqlalchemy.schema.ForeignKey(
            model.Meta.tablename + "." + pk_alias,
            ondelete="CASCADE",
            onupdate="CASCADE",
            name=
            f"fk_{model_field.through.Meta.tablename}_{model.Meta.tablename}"
            f"_{field_name}_{pk_alias}",
        ),
    )
    model_field.through.Meta.columns.append(column)
    model_field.through.Meta.table.append_column(column)
Exemple #3
0
def validate_related_names_in_relations(  # noqa CCR001
        model_fields: Dict, new_model: Type["Model"]) -> None:
    """
    Performs a validation of relation_names in relation fields.
    If multiple fields are leading to the same related model
    only one can have empty related_name param
    (populated by default as model.name.lower()+'s').
    Also related_names have to be unique for given related model.

    :raises ModelDefinitionError: if validation of related_names fail
    :param model_fields: dictionary of declared ormar model fields
    :type model_fields: Dict[str, ormar.Field]
    :param new_model:
    :type new_model: Model class
    """
    already_registered: Dict[str, List[Optional[str]]] = dict()
    for field in model_fields.values():
        if issubclass(field, ormar.ForeignKeyField):
            to_name = (field.to.get_name() if
                       not field.to.__class__ == ForwardRef else str(field.to))
            previous_related_names = already_registered.setdefault(to_name, [])
            if field.related_name in previous_related_names:
                raise ormar.ModelDefinitionError(
                    f"Multiple fields declared on {new_model.get_name(lower=False)} "
                    f"model leading to {field.to.get_name(lower=False)} model without "
                    f"related_name property set. \nThere can be only one relation with "
                    f"default/empty name: '{new_model.get_name() + 's'}'"
                    f"\nTip: provide different related_name for FK and/or M2M fields"
                )
            previous_related_names.append(field.related_name)
Exemple #4
0
def check_required_meta_parameters(new_model: Type["Model"]) -> None:
    """
    Verifies if ormar.Model has database and metadata set.

    Recreates Connection pool for sqlite3

    :param new_model: newly declared ormar Model
    :type new_model: Model class
    """
    if not hasattr(new_model.Meta, "database"):
        if not getattr(new_model.Meta, "abstract", False):
            raise ormar.ModelDefinitionError(
                f"{new_model.__name__} does not have database defined.")

    else:
        substitue_backend_pool_for_sqlite(new_model=new_model)

    if not hasattr(new_model.Meta, "metadata"):
        if not getattr(new_model.Meta, "abstract", False):
            raise ormar.ModelDefinitionError(
                f"{new_model.__name__} does not have metadata defined.")
Exemple #5
0
def check_pk_column_validity(
    field_name: str, field: "BaseField", pkname: Optional[str]
) -> Optional[str]:
    """
    Receives the field marked as primary key and verifies if the pkname
    was not already set (only one allowed per model) and if field is not marked
    as pydantic_only as it needs to be a database field.

    :raises ModelDefintionError: if pkname already set or field is pydantic_only
    :param field_name: name of field
    :type field_name: str
    :param field: ormar.Field
    :type field: BaseField
    :param pkname: already set pkname
    :type pkname: Optional[str]
    :return: name of the field that should be set as pkname
    :rtype: str
    """
    if pkname is not None:
        raise ormar.ModelDefinitionError("Only one primary key column is allowed.")
    if field.pydantic_only:
        raise ormar.ModelDefinitionError("Primary key column cannot be pydantic only")
    return field_name
Exemple #6
0
def populate_meta_tablename_columns_and_pk(
    name: str, new_model: Type["Model"]
) -> Type["Model"]:
    """
    Sets Model tablename if it's not already set in Meta.
    Default tablename if not present is class name lower + s (i.e. Bed becomes -> beds)

    Checks if Model's Meta have pkname and columns set.
    If not calls the sqlalchemy_columns_from_model_fields to populate
    columns from ormar.fields definitions.

    :raises ModelDefinitionError: if pkname is not present raises ModelDefinitionError.
    Each model has to have pk.

    :param name: name of the current Model
    :type name: str
    :param new_model: currently constructed Model
    :type new_model: ormar.models.metaclass.ModelMetaclass
    :return: Model with populated pkname and columns in Meta
    :rtype: ormar.models.metaclass.ModelMetaclass
    """
    tablename = name.lower() + "s"
    new_model.Meta.tablename = (
        new_model.Meta.tablename if hasattr(new_model.Meta, "tablename") else tablename
    )
    pkname: Optional[str]

    if hasattr(new_model.Meta, "columns"):
        columns = new_model.Meta.columns
        pkname = new_model.Meta.pkname
    else:
        pkname, columns = sqlalchemy_columns_from_model_fields(
            new_model.Meta.model_fields, new_model
        )

    if pkname is None:
        raise ormar.ModelDefinitionError("Table has to have a primary key.")

    new_model.Meta.columns = columns
    new_model.Meta.pkname = pkname
    if not new_model.Meta.orders_by:
        # by default we sort by pk name if other option not provided
        new_model.Meta.orders_by.append(pkname)
    return new_model