Proposal: filter by multiple values, case-insensitive

2022-10-29 Thread Jeremy Nauta
Hi all, I'd like to propose a new django filter: *__iin*. As the name 
implies, this filter finds results that match one of the given values, 
ignoring case. In other words, this is a hybrid of *__in *and *__iexact*.

It surprises me that there are filters for* qs.filter(field__iexact='text')*
, *qs.filter(field__icontains='text')*, and *qs.filter(field__in=[**'text']*
*)*, yet there is no *qs.filter(field__iin=[**'text']**)* filter.

I've made a POC for this field in postgres and it seems quite 
straightforward. I'd be happy to try porting into the Django codebase, but 
first I wanted to see if there is interest and/or feedback! Please let me 
know what you think.


























*from django.db.models import Fieldfrom django.db.models.lookups import 
In@Field.register_lookupclass IIn(In):"""Case-insensitive version 
of `__in` filters. Adapted from `In` and `IExact` transformers."""
lookup_name = 'iin'def process_lhs(self, *args, **kwargs):sql, 
params = super().process_lhs(*args, **kwargs)sql = f'LOWER({sql})'  
  return sql, paramsdef process_rhs(self, qn, connection):
rhs, params = super().process_rhs(qn, connection)params = 
tuple(p.lower() for p in params)return rhs, params*

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/bf623b99-9032-4ada-9739-ef63f2982e6an%40googlegroups.com.


Re: Generated Field

2022-12-24 Thread Jeremy Nauta
I'd love to help implement this if we can find a rough syntax! I've made a 
proof of concept in Postgres, and there are two outstanding limitations to 
address:

- The generated field value is not set until the model is reloaded from the 
database
- The `GENERATED ALWAYS` expression requires an argument to be injected in 
the the sql expression, but this is not currently possible

*from django.db.backends.utils import CursorWrapper*

*from django.db.models import Expression, Field*

*from django.db.models.sql import Query*



*class GeneratedField(Field):*

*"""*

*Wrapper field used to support generated columns in postgres.*

*"""*


*def __init__(self, expression: Expression, db_collation: str = None, 
*args, **kwargs):*

*"""*

*:param expression: DB expression used to calculate the 
auto-generated field value*

*"""*


*self.expression = expression*

*self.db_collation = db_collation*


*kwargs['editable'] = False  # This field can never be edited*

*kwargs['blank'] = True  # This field never requires a value to be 
set*

*kwargs['null'] = True  # This field never requires a value to be 
set*


*super().__init__(*args, **kwargs)*


*def _compile_expression(self, cursor: CursorWrapper, sql: str, params: 
dict):*

*"""*

*Compiles SQL and its associated parameters into a full SQL query. 
Usually sql params are kept*

*separate until `cursor.execute()` is called, but this is not 
possible since this function*

*must return a single sql string.*

*"""*


*return cursor.mogrify(sql, params).decode()*


*def db_type(self, connection):*

*"""*

*Called when calculating SQL to create DB column (e.g. DB 
migrations)*

*
https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.db_type*

*"""*


*db_type = 
self.expression.output_field.db_type(connection=connection)*


*# Convert any F() references to concrete field names*

*query = Query(model=self.model, alias_cols=False)*

*expression = self.expression.resolve_expression(query, 
allow_joins=False)*


*# Compile expression into SQL*

*expression_sql, params = expression.as_sql(*

*compiler=connection.ops.compiler('SQLCompiler')(*

*query, connection=connection, using=None*

*),*

*connection=connection,*

*)*


*with connection.cursor() as cursor:*

*expression_sql = self._compile_expression(*

*cursor=cursor, sql=expression_sql, params=params*

*)*


*return f'{db_type} GENERATED ALWAYS AS ({expression_sql}) STORED'*


*def rel_db_type(self, connection):*

*"""*

*Called when calculating SQL to reference DB column*

*
https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.rel_db_type*

*"""*

*return self.expression.output_field.db_type(connection=connection)*


*def deconstruct(self):*

*"""*

*Add custom field properties to allow migrations to deconstruct 
field*

*
https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.deconstruct*

*"""*

*name, path, args, kwargs = super().deconstruct()*

*kwargs['expression'] = self.expression*

*if self.db_collation is not None:*

*kwargs['db_collation'] = self.db_collation*

*return name, path, args, kwargs*



*class GeneratedFieldQuerysetMixin:*

*"""*

*Must be added to queryset classes*

*"""*


*def _insert(self, objs, fields, *args, **kwargs):*

*if getattr(self.model, '_generated_fields', None) and fields:*

*# Don't include generated fields when performing a 
`model.objects.bulk_create()`*

*fields = [f for f in fields if f not in 
self.model._generated_fields()]*


*return super()._insert(objs, fields, *args, **kwargs)*



*class GeneratedFieldModelMixin:*

*"""*

*Must be added to model class*

*"""*


*def _generated_fields(cls) -> list[Field]:*


*"""*

*:return all fields of the model that are generated*

*"""*


*return [*

*f*

*for f in cls._meta.fields*

*if isinstance(f, GeneratedField)*

*]*


*def _do_insert(self, manager, using, fields, *args, **kwargs):*

*generated_fields = self._generated_fields()*

*if generated_fields and fields:*

*# Don't include generated fields when performing a `save()` or 
`create()`*

*fields = [f for f in fields if f not in generated_fields]*


*return super()._do_insert(manager, using, fields, *args, **kwargs)*


*def _do_update(self, base_qs, using, pk_val, values, *args, **kwargs):*

*generated_fields = self._generated_fields()*

*

Re: Generated Field

2023-01-01 Thread Jeremy Nauta
I have implemented a first draft for this feature! Feedback and ideas are 
greatly appreciated.

https://github.com/django/django/pull/16417
https://code.djangoproject.com/ticket/34238

On Saturday, December 24, 2022 at 8:56:38 p.m. UTC-7 schinckel wrote:

> I believe there are a bunch of similarities between the requirements of 
> generated fields and my project django-shared-property: 
> https://django-shared-property.readthedocs.io/en/latest/
>
> On Sunday, December 25, 2022 at 10:23:10 AM UTC+10:30 jeremy...@gmail.com 
> wrote:
>
>> I'd love to help implement this if we can find a rough syntax! I've made 
>> a proof of concept in Postgres, and there are two outstanding limitations 
>> to address:
>>
>> - The generated field value is not set until the model is reloaded from 
>> the database
>> - The `GENERATED ALWAYS` expression requires an argument to be injected 
>> in the the sql expression, but this is not currently possible
>>
>> *from django.db.backends.utils import CursorWrapper*
>>
>> *from django.db.models import Expression, Field*
>>
>> *from django.db.models.sql import Query*
>>
>>
>>
>> *class GeneratedField(Field):*
>>
>> *"""*
>>
>> *Wrapper field used to support generated columns in postgres.*
>>
>> *"""*
>>
>>
>> *def __init__(self, expression: Expression, db_collation: str = None, 
>> *args, **kwargs):*
>>
>> *"""*
>>
>> *:param expression: DB expression used to calculate the 
>> auto-generated field value*
>>
>> *"""*
>>
>>
>> *self.expression = expression*
>>
>> *self.db_collation = db_collation*
>>
>>
>> *kwargs['editable'] = False  # This field can never be edited*
>>
>> *kwargs['blank'] = True  # This field never requires a value to 
>> be set*
>>
>> *kwargs['null'] = True  # This field never requires a value to be 
>> set*
>>
>>
>> *super().__init__(*args, **kwargs)*
>>
>>
>> *def _compile_expression(self, cursor: CursorWrapper, sql: str, 
>> params: dict):*
>>
>> *"""*
>>
>> *Compiles SQL and its associated parameters into a full SQL 
>> query. Usually sql params are kept*
>>
>> *separate until `cursor.execute()` is called, but this is not 
>> possible since this function*
>>
>> *must return a single sql string.*
>>
>> *"""*
>>
>>
>> *return cursor.mogrify(sql, params).decode()*
>>
>>
>> *def db_type(self, connection):*
>>
>> *"""*
>>
>> *Called when calculating SQL to create DB column (e.g. DB 
>> migrations)*
>>
>> *
>> https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.db_type
>>  
>> *
>>
>> *"""*
>>
>>
>> *db_type = 
>> self.expression.output_field.db_type(connection=connection)*
>>
>>
>> *# Convert any F() references to concrete field names*
>>
>> *query = Query(model=self.model, alias_cols=False)*
>>
>> *expression = self.expression.resolve_expression(query, 
>> allow_joins=False)*
>>
>>
>> *# Compile expression into SQL*
>>
>> *expression_sql, params = expression.as_sql(*
>>
>> *compiler=connection.ops.compiler('SQLCompiler')(*
>>
>> *query, connection=connection, using=None*
>>
>> *),*
>>
>> *connection=connection,*
>>
>> *)*
>>
>>
>> *with connection.cursor() as cursor:*
>>
>> *expression_sql = self._compile_expression(*
>>
>> *cursor=cursor, sql=expression_sql, params=params*
>>
>> *)*
>>
>>
>> *return f'{db_type} GENERATED ALWAYS AS ({expression_sql}) 
>> STORED'*
>>
>>
>> *def rel_db_type(self, connection):*
>>
>> *"""*
>>
>> *Called when calculating SQL to reference DB column*
>>
>> *
>> https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.rel_db_type
>>  
>> *
>>
>> *"""*
>>
>> *return 
>> self.expression.output_field.db_type(connection=connection)*
>>
>>
>> *def deconstruct(self):*
>>
>> *"""*
>>
>> *Add custom field properties to allow migrations to deconstruct 
>> field*
>>
>> *
>> https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.deconstruct
>>  
>> *
>>
>> *"""*
>>
>> *name, path, args, kwargs = super().deconstruct()*
>>
>> *kwargs['expression'] = self.expression*
>>
>> *if self.db_collation is not None:*
>>
>> *kwargs['db_collation'] = self.db_collation*
>>
>> *return name, path, args, kwargs*
>>
>>
>>
>> *class GeneratedFieldQuerysetMixin:*
>>
>> *"""*
>>
>> *Must be added to queryset classes*
>>
>> *"""*
>>
>>
>> *def _insert(self, 

Re: models.CalculatedField feature

2023-01-02 Thread Jeremy Nauta
FYI I have posted a tentative first draft of this feature, feedback is 
greatly appreciated!

https://github.com/django/django/pull/16417
https://code.djangoproject.com/ticket/31300

On Saturday, February 3, 2018 at 5:06:08 p.m. UTC-7 raider...@gmail.com 
wrote:

> Hey, 
>
> So has this stalled? Because this looked really great, and I was looking 
> forward to seeing it!
>
>
> On Monday, November 20, 2017 at 10:31:31 PM UTC-5, 
> ilya.ka...@jetbrains.com wrote:
>
>> Thank you all.
>>
>> I've created a feature request 
>> https://code.djangoproject.com/ticket/28822 and will try to implement in 
>> my Django fork next week. I am not Django source code guru, so any help is 
>> welcome:)
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/6b2c9692-390f-41b0-96d0-eab170cdb15dn%40googlegroups.com.