from typing import Any
from django.db import models
from django.core import exceptions, validators
from django.db.models.fields import PositiveIntegerRelDbTypeMixin
from .cryptography import encrypt, decrypt
from ast import literal_eval
import datetime
from django import forms
from django.utils import timezone
from uuid import UUID
from django.utils.translation import gettext_lazy as _
from django.db.models.lookups import StartsWith as StartWith, FieldGetDbPrepValueMixin
"""
to_python() make validations & checks type of the data
get_db_prep_value() encrypts the data
from_db_value() decrypts the data returned from the db
pre_save() generates date ,datetime,time for the respestive fields
get_db_prep_save() saves the value into db
"""
[docs]class StartsWith(FieldGetDbPrepValueMixin, StartWith):
pass
[docs]class CharField(models.CharField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return decrypt(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(value)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class BooleanField(models.BooleanField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return literal_eval(decrypt(value))
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class DateField(models.DateField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
# Casts dates into the format expected by the backend
if not prepared:
value = self.get_prep_value(value)
return encrypt(connection.ops.adapt_datefield_value(value))
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
try:
return datetime.date.fromisoformat(decrypt(value))
except AttributeError:
from timestring import Date
return Date(decrypt(value)).date.date()
[docs] def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.date.today()
setattr(model_instance, self.attname, value)
return value
else:
return super().pre_save(model_instance, add)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class DateTimeField(models.DateTimeField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return super().get_prep_value(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
# Casts dates into the format expected by the backend
if not prepared:
value = self.get_prep_value(value)
return encrypt(connection.ops.adapt_datetimefield_value(value))
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
try:
return datetime.datetime.fromisoformat(decrypt(value))
except AttributeError:
from timestring import Date
return Date(decrypt(value)).date
[docs] def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = timezone.now()
setattr(model_instance, self.attname, value)
return value
else:
return super().pre_save(model_instance, add)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class TimeField(models.TimeField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
# Casts dates into the format expected by the backend
if not prepared:
value = self.get_prep_value(value)
return encrypt(connection.ops.adapt_timefield_value(value))
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
try:
return datetime.time.fromisoformat(decrypt(value))
except AttributeError:
from timestring import Date
return Date(decrypt(value)).date.time()
[docs] def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.datetime.now().time()
setattr(model_instance, self.attname, value)
return value
else:
return super().pre_save(model_instance, add)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class DecimalField(models.DecimalField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return super().to_python(value)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def get_db_prep_save(self, value, connection,):
return encrypt(connection.ops.adapt_decimalfield_value(self.get_prep_value(value), self.max_digits, self.decimal_places))
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return float(decrypt(value))
# return value
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class EmailField(CharField):
default_validators = [validators.validate_email]
description = _("Email address")
def __init__(self, *args, **kwargs):
# max_length=254 to be compliant with RFCs 3696 and 5321
kwargs.setdefault('max_length', 254)
super().__init__(*args, **kwargs)
[docs]class FloatField(models.FloatField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return float(decrypt(value))
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(value)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class IntegerField(models.IntegerField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return int(decrypt(value))
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(value)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class BigIntegerField(IntegerField):
error_messages = {
'invalid': _('“%(value)s” value must be less than %(max) & greater than %(min).'),
}
description = _("Big (8 byte) integer")
MAX_BIGINT = 9223372036854775807
[docs] def to_python(self, value: Any) -> Any:
value = self.clean(super().to_python(value), None)
if value < (-self.MAX_BIGINT-1) or value > self.MAX_BIGINT:
raise exceptions.ValidationError(self.error_messages['invalid'], code='invalid',
params={'value': value, 'max': self.MAX_BIGINT, 'min': (-self.MAX_BIGINT-1)})
return value
[docs]class GenericIPAddressField(models.GenericIPAddressField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(connection.ops.adapt_ipaddressfield_value(value))
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return decrypt(value)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
description = _('Positive big integer')
error_messages = {
'invalid': _('“%(value)s” value must be less than greater than %(min).'),
}
[docs] def to_python(self, value: Any) -> Any:
value = self.clean(super().to_python(value), None)
if value < 0:
raise exceptions.ValidationError(self.error_messages['invalid'], code='invalid',
params={'value': value, 'min': 0})
return value
[docs]class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
description = _("Positive integer")
error_messages = {
'invalid': _('“%(value)s” value must be less than greater than %(min).'),
}
[docs] def to_python(self, value: Any) -> Any:
value = self.clean(super().to_python(value), None)
if value < 0:
raise exceptions.ValidationError(self.error_messages['invalid'], code='invalid',
params={'value': value, 'min': 0})
return value
[docs]class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
description = _("Positive small integer")
error_messages = {
'invalid': _('“%(value)s” value must be less than greater than %(min).'),
}
[docs] def to_python(self, value: Any) -> Any:
value = self.clean(super().to_python(value), None)
if value < 0:
raise exceptions.ValidationError(self.error_messages['invalid'], code='invalid',
params={'value': value, 'min': 0})
return value
[docs]class SlugField(CharField):
default_validators = [validators.validate_slug]
description = _("Slug (up to %(max_length)s)")
def __init__(self, *args, max_length=50, db_index=True, allow_unicode=False, **kwargs):
self.allow_unicode = allow_unicode
if self.allow_unicode:
self.default_validators = [validators.validate_unicode_slug]
super().__init__(*args, max_length=max_length, db_index=db_index, **kwargs)
[docs] def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if kwargs.get("max_length") == 50:
del kwargs['max_length']
if self.db_index is False:
kwargs['db_index'] = False
else:
del kwargs['db_index']
if self.allow_unicode is not False:
kwargs['allow_unicode'] = self.allow_unicode
return name, path, args, kwargs
[docs] def get_internal_type(self):
return "SlugField"
[docs]class SmallIntegerField(IntegerField):
description = _("Small integer")
[docs]class TextField(models.TextField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return decrypt(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(value)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class URLField(CharField):
default_validators = [validators.URLValidator()]
description = _("URL")
def __init__(self, verbose_name=None, name=None, **kwargs):
kwargs.setdefault('max_length', 200)
super().__init__(verbose_name, name, **kwargs)
[docs] def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if kwargs.get("max_length") == 200:
del kwargs['max_length']
return name, path, args, kwargs
[docs]class BinaryField(models.BinaryField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
if isinstance(value, str):
value = bytes(value, "UTF-8")
elif isinstance(value, memoryview):
value = bytes(value)
value = self.clean(value, None)
return value
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return bytes.fromhex(decrypt(value))
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(bytes(connection.Database.Binary(value)).hex())
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class UUIDField(models.UUIDField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return UUID(hex=decrypt(value))
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(value.hex)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
[docs]class FilePathField(models.FilePathField):
[docs] def get_internal_type(self) -> str:
return "TextField"
[docs] def to_python(self, value: Any) -> Any:
return self.clean(super().to_python(value), None)
[docs] def get_prep_value(self, value: Any) -> Any:
return self.to_python(value)
[docs] def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
return decrypt(value)
[docs] def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return encrypt(value)
[docs] def clean(self, value, model_instance):
"""
Convert the value's type and run validation. Validation errors
from to_python() and validate() are propagated. Return the correct
value if no error is raised.
"""
self.validate(value, model_instance)
self.run_validators(value)
return value
CharField.register_lookup(StartsWith)
BooleanField.register_lookup(StartsWith)
DateField.register_lookup(StartsWith)
DateTimeField.register_lookup(StartsWith)
EmailField.register_lookup(StartsWith)
GenericIPAddressField.register_lookup(StartsWith)
SlugField.register_lookup(StartsWith)
TextField.register_lookup(StartsWith)
URLField.register_lookup(StartsWith)
BinaryField.register_lookup(StartsWith)
UUIDField.register_lookup(StartsWith)
FilePathField.register_lookup(StartsWith)
DateField.register_lookup(StartsWith, lookup_name="date")
DateTimeField.register_lookup(StartsWith, lookup_name="date")
TimeField.register_lookup(StartsWith, lookup_name="time")