Skip to content

Commit

Permalink
caching queryset & cleanup cloning and initialization & special chara…
Browse files Browse the repository at this point in the history
…cters (#206)

* Changes:

- cache select_related expression and select expression
- make QuerySet keyword only for the second plus argument
- keyword arguments match now the function names, old names are
  deprecated
- copy by calling init

* Changes:

- fix typings
- make select_related variadic and deprecate the former call interface

* Changes:

- use new variadic select_related call in tests
- update release notes

* Changed:

- copy cached select_related from temporary subqueries

* fix get_raw

* bump version

* Changes:

- allow special characters in model names
- honor column_name in multi column fields

* fix typings by switching type

* update tests for special chars

* Changes:

- remove rendundant tests
- fks can now point to columns with special chars
  • Loading branch information
devkral authored Oct 17, 2024
1 parent 2b08b94 commit 2f9403d
Show file tree
Hide file tree
Showing 20 changed files with 687 additions and 261 deletions.
4 changes: 3 additions & 1 deletion docs/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Check the [primary_key](./models.md#restrictions-with-primary-keys) restrictions
- `skip_reflection_type_check` - A boolean. Default False. Skip reflection column type check.
- `unique` - A boolean. Determine if a unique constraint should be created for the field.
Check the [unique_together](./models.md#unique-together) for more details.
- `column_name` - A string. Database name of the column (by default the same as the name)
- `column_name` - A string. Database name of the column (by default the same as the name).
- `comment` - A comment to be added with the field in the SQL database.
- `secret` - A special attribute that allows to call the [exclude_secrets](./queries/secrets.md#exclude-secrets) and avoid
accidental leakage of sensitive data.
Expand Down Expand Up @@ -502,6 +502,7 @@ from `edgy`.
```
* `relation_fn` - Optionally drop a function which returns a Relation for the reverse side. This will be used by the RelatedField (if it is created). Used by the ManyToMany field.
* `reverse_path_fn` - Optionally drop a function which handles the traversal from the reverse side. Used by the ManyToMany field.
- `column_name` - A string. Base database name of the column (by default the same as the name). Useful for models with special characters in their name.


!!! Note
Expand Down Expand Up @@ -568,6 +569,7 @@ class MyModel(edgy.Model):
* `related_name` - The name to use for the relation from the related object back to this one.
* `through` - The model to be used for the relationship. Edgy generates the model by default
if None is provided or `through` is an abstract model.
* `through_tablename` - Custom tablename for `through`. E.g. when special characters are used in model names.
* `embed_through` - When traversing, embed the through object in this attribute. Otherwise it is not accessable from the result.
if an empty string was provided, the old behaviour is used to query from the through model as base (default).
if False, the base is transformed to the target and source model (full proxying). You cannot select the through model via path traversal anymore (except from the through model itself).
Expand Down
11 changes: 10 additions & 1 deletion docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ hide:

# Release Notes

## 0.18.2
## 0.19.0

### Added

- New `SET_DEFAULT`, and `PROTECT` to `on_delete` in the ForeignKey.
- New `through_tablename` parameter for ManyToMany.

### Removed

Expand All @@ -21,6 +22,14 @@ hide:
- Allow setting registry = False, for disabling retrieving the registry from parents.
- Removed unecessary warning for ManyToMany.
- Add warnings for problematic combinations in ForeignKey.
- Make QuerySet nearly keyword only and deprecate keywords not matching function names.
- Clone QuerySet via `__init__`.
- Make select_related variadic and deprecate former call taking a Sequence.
- Improved QuerySet caching.

### Fixed

- Multi-column fields honor now `column_name`. This allows special characters in model names.

## 0.18.1

Expand Down
2 changes: 1 addition & 1 deletion edgy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.18.1"
__version__ = "0.19.0"

from .cli.base import Migrate
from .conf import settings
Expand Down
4 changes: 3 additions & 1 deletion edgy/core/connection/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,9 @@ async def create_all(

async def drop_all(self, databases: Sequence[Union[str, None]] = (None,)) -> None:
if self.db_schema:
await self.schema.drop_schema(self.db_schema, True, True, databases=databases)
await self.schema.drop_schema(
self.db_schema, cascade=True, if_exists=True, databases=databases
)
else:
for database in databases:
db = self.database if database is None else self.extra[database]
Expand Down
20 changes: 13 additions & 7 deletions edgy/core/db/fields/file_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@


class ConcreteFileField(BaseCompositeField):
column_name: str = ""
multi_process_safe: bool = True
field_file_class: type[FieldFile]
_generate_name_fn: Optional[
Expand Down Expand Up @@ -164,15 +165,18 @@ def to_model(

def get_columns(self, field_name: str) -> Sequence[sqlalchemy.Column]:
model = ColumnDefinitionModel.model_validate(self, from_attributes=True)
column_name = self.column_name or field_name
return [
sqlalchemy.Column(
field_name,
model.column_type,
**model.model_dump(by_alias=True, exclude_none=True),
key=field_name,
type_=model.column_type,
name=column_name,
**model.model_dump(by_alias=True, exclude_none=True, exclude={"column_name"}),
),
sqlalchemy.Column(
f"{field_name}_storage",
sqlalchemy.String(length=20, collation=self.column_type.collation),
key=f"{field_name}_storage",
name=f"{column_name}_storage",
type_=sqlalchemy.String(length=20, collation=self.column_type.collation),
default=self.storage.name,
),
]
Expand All @@ -181,6 +185,7 @@ def get_embedded_fields(
self, name: str, fields: dict[str, "BaseFieldType"]
) -> dict[str, "BaseFieldType"]:
retdict: dict[str, Any] = {}
column_name = self.column_name or name
# TODO: use embed_field
if self.with_size:
size_name = f"{name}_size"
Expand All @@ -191,6 +196,7 @@ def get_embedded_fields(
exclude=True,
read_only=True,
name=size_name,
column_name=f"{column_name}_size",
owner=self.owner,
)
if self.with_approval:
Expand All @@ -200,7 +206,7 @@ def get_embedded_fields(
null=False,
default=False,
exclude=True,
column_name=f"{name}_ok",
column_name=f"{column_name}_ok",
name=approval_name,
owner=self.owner,
)
Expand All @@ -209,7 +215,7 @@ def get_embedded_fields(
if metadata_name not in fields:
retdict[metadata_name] = JSONField(
null=False,
column_name=f"{name}_mname",
column_name=f"{column_name}_mname",
name=metadata_name,
owner=self.owner,
default=dict,
Expand Down
19 changes: 14 additions & 5 deletions edgy/core/db/fields/foreign_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
class BaseForeignKeyField(BaseForeignKey):
force_cascade_deletion_relation: bool = False
relation_has_post_delete_callback: bool = False
# overwrite for sondercharacters
column_name: Optional[str] = None

def __init__(
self,
Expand Down Expand Up @@ -277,6 +279,12 @@ def get_fk_field_name(self, name: str, fieldname: str) -> str:
return name
return f"{name}_{fieldname}"

def get_fk_column_name(self, name: str, fieldname: str) -> str:
name = self.column_name or name
if len(self.related_columns) == 1:
return name
return f"{name}_{fieldname}"

def from_fk_field_name(self, name: str, fieldname: str) -> str:
if len(self.related_columns) == 1:
return next(iter(self.related_columns.keys()))
Expand All @@ -285,14 +293,15 @@ def from_fk_field_name(self, name: str, fieldname: str) -> str:
def get_columns(self, name: str) -> Sequence[sqlalchemy.Column]:
target = self.target
columns = []
for column_name, related_column in self.related_columns.items():
for column_key, related_column in self.related_columns.items():
if related_column is None:
related_column = target.table.columns[column_name]
fkcolumn_name = self.get_fk_field_name(name, column_name)
related_column = target.table.columns[column_key]
fkcolumn_name = self.get_fk_field_name(name, column_key)
# use the related column as reference
fkcolumn = sqlalchemy.Column(
fkcolumn_name,
related_column.type,
key=fkcolumn_name,
type_=related_column.type,
name=self.get_fk_column_name(name, related_column.name),
primary_key=self.primary_key,
autoincrement=False,
nullable=related_column.nullable or self.null,
Expand Down
4 changes: 3 additions & 1 deletion edgy/core/db/fields/many_to_many.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
from_fields: Sequence[str] = (),
from_foreign_key: str = "",
through: Union[str, type["BaseModelType"]] = "",
through_tablename: str = "",
embed_through: Union[str, Literal[False]] = "",
**kwargs: Any,
) -> None:
Expand All @@ -37,6 +38,7 @@ def __init__(
self.from_fields = from_fields
self.from_foreign_key = from_foreign_key
self.through = through
self.through_tablename = through_tablename
self.embed_through = embed_through

@cached_property
Expand Down Expand Up @@ -185,7 +187,7 @@ def create_through_model(self) -> None:
if not self.to_foreign_key:
self.to_foreign_key = to_name.lower()

tablename = f"{owner_name.lower()}s_{to_name}s".lower()
tablename = self.through_tablename or f"{self.from_foreign_key}s_{self.to_foreign_key}s"
meta_args = {
"tablename": tablename,
"registry": self.owner.meta.registry,
Expand Down
Loading

0 comments on commit 2f9403d

Please sign in to comment.