I'm trying to build up my first project in Django and I'm having some issues while creating an entry into my model Prevision (including sales forecasts), which has a uniqueConstraint to ensure that there is only one forecast between a Supplier (model "Representada") and a Customer (model "Cliente") for a give year (field "Ano").
My models.py file is as follows:
from django.db import models
from django.db.models import CheckConstraint, Q, F, UniqueConstraint
from django.db.models.signals import pre_save
from django.dispatch import receiver
from datetime import datetime
# Create your models here.
class Empresa (models.Model):
CIF = models.CharField(max_length=9, unique=True, blank=True)
Nombre = models.CharField(max_length=64)
Telefono = models.CharField(max_length=16, blank=True)
Email = models.EmailField(blank=True)
Direccion = models.CharField(max_length=64)
Ciudad = models.CharField(max_length=32)
CP = models.PositiveIntegerField(default=1)
Provincia = models.IntegerField(default=1)
Contacto = models.CharField(max_length=64, blank=True)
Observaciones = models.TextField(blank=True)
def __str__(self):
return f"{self.Nombre}"
class Meta:
abstract = True
verbose_name_plural = "Empresas"
constraints = [
]
class Representada (Empresa):
Fecha_Alta = models.DateField()
Fecha_Baja = models.DateField(blank=True, null=True)
Porcentaje_Comision = models.DecimalField(max_digits=5, decimal_places=2)
class Meta:
verbose_name_plural = "Representadas"
constraints = [
CheckConstraint(
check = Q(Fecha_Baja__isnull=True)|Q(Fecha_Alta__lte=F('Fecha_Baja')),
name = 'Comprobar fecha de alta y baja de representada',
),
]
class Cliente (Empresa):
Hay_Toro = models.BooleanField(blank=True)
Inicio_Horario_Descarga = models.TimeField(blank=True, null=True)
Fin_Horario_Descarga = models.TimeField(blank=True, null=True)
def __str__(self):
return f"{self.Nombre} ({self.Ciudad})"
class Meta:
verbose_name_plural = "Clientes"
constraints = [
CheckConstraint(
check = Q(Inicio_Horario_Descarga__isnull=True, Fin_Horario_Descarga__isnull=True)|Q(Inicio_Horario_Descarga__isnull=False, Fin_Horario_Descarga__isnull=False, Inicio_Horario_Descarga__lte=F('Fin_Horario_Descarga')),
name = 'Comprobar horario de descarga',
),
]
class Prevision (models.Model):
Ano = models.PositiveSmallIntegerField(default=datetime.now().year)
Cliente = models.ForeignKey("Cliente", on_delete=models.CASCADE)
Representada = models.ForeignKey("Representada", on_delete=models.CASCADE)
Importe = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
def __str__(self):
return f"Año: {self.Ano} Representada: {self.Representada} Cliente: {self.Cliente}"
class Meta:
verbose_name_plural = "Previsiones"
constraints = [
UniqueConstraint(
fields=['Ano', 'Cliente', 'Representada'], name='Primary_Key_Prevision'
)
]
@receiver(pre_save, sender=Prevision)
def comprobar_ano_prevision(sender, instance, ** kwargs) :
representada = Representada.objects.get(id = instance.Representada)
if representada.Fecha_Baja is not NONE and representada.Fecha_Baja.year < instance.Año:
raise Exception("No se puede generar una previsión para una representada dada de baja")
Once models are properly migrated, I enter into admin mode and register a Supplier (i.e. 'Representada') and a Customer (i.e. 'Cliente') with no issues. Once I have one of each registered in the database, I try to do the same with a forecast (i.e. 'Prevision'), however, when trying to do this via admin mode in the web browser I got a Server Error (500) in return.
Trying to understand a bit better what's going on, I run a shell to have a closer look to the error:
(Desarrollo) (...)\Desarrollo\Company>py manage.py shell
Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from BaseDatos.models import Representada, Prevision, Cliente
>>> Cliente.objects.all().values()
<QuerySet [{'id': 4, 'CIF': '2', 'Nombre': 'Cli1', 'Telefono': '', 'Email': '', 'Direccion': '2', 'Ciudad': '2', 'CP': 13300, 'Provincia': 13, 'Contacto': '', 'Observaciones': '', 'Hay_Toro': False, 'Inicio_Horario_Descarga': None, 'Fin_Horario_Descarga': None}]>
>>> Representada.objects.all().values()
<QuerySet [{'id': 3, 'CIF': '1', 'Nombre': 'Rep1', 'Telefono': '', 'Email': '', 'Direccion': '1', 'Ciudad': '1', 'CP': 13300, 'Provincia': 13, 'Contacto': '', 'Observaciones': '', 'Fecha_Alta': datetime.date(2024, 6, 26), 'Fecha_Baja': None, 'Porcentaje_Comision': Decimal('1.00')}]>
>>> Prevision.objects.all().values()
<QuerySet []>
>>> prev = Prevision(Ano='2023', Cliente=Cliente.objects.get(id='4'), Representada=Representada.objects.get(id='3'), Importe='100.00')
>>> prev.save()
Traceback (most recent call last):
File "(...)\Desarrollo\Lib\site-packages\django\db\models\fields\__init__.py", line 2117, in get_prep_value
return int(value)
^^^^^^^^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'Representada'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "(...)\Desarrollo\Lib\site-packages\django\db\models\base.py", line 822, in save
self.save_base(
File "(...)\Desarrollo\Lib\site-packages\django\db\models\base.py", line 889, in save_base
pre_save.send(
File "(...)\Desarrollo\Lib\site-packages\django\dispatch\dispatcher.py", line 189, in send
response = receiver(signal=self, sender=sender, **named)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Company\BaseDatos\models.py", line 290, in comprobar_ano_prevision
representada = Representada.objects.get(id = instance.Representada)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\query.py", line 635, in get
clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\query.py", line 1476, in filter
return self._filter_or_exclude(False, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\query.py", line 1494, in _filter_or_exclude
clone._filter_or_exclude_inplace(negate, args, kwargs)
File "(...)\Desarrollo\Lib\site-packages\django\db\models\query.py", line 1501, in _filter_or_exclude_inplace
self._query.add_q(Q(*args, **kwargs))
File "(...)\Desarrollo\Lib\site-packages\django\db\models\sql\query.py", line 1613, in add_q
clause, _ = self._add_q(q_object, self.used_aliases)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\sql\query.py", line 1645, in _add_q
child_clause, needed_inner = self.build_filter(
^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\sql\query.py", line 1559, in build_filter
condition = self.build_lookup(lookups, col, value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\sql\query.py", line 1389, in build_lookup
lookup = lookup_class(lhs, rhs)
^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\lookups.py", line 30, in __init__
self.rhs = self.get_prep_lookup()
^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\lookups.py", line 364, in get_prep_lookup
return super().get_prep_lookup()
^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\lookups.py", line 88, in get_prep_lookup
return self.lhs.output_field.get_prep_value(self.rhs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(...)\Desarrollo\Lib\site-packages\django\db\models\fields\__init__.py", line 2119, in get_prep_value
raise e.__class__(
TypeError: Field 'id' expected a number but got <Representada: Rep1>.
>>>
If I try to use a number instead of an instance, as suggested by the TypeError, things don't go any better:
>>> prev = Prevision(Ano='2023', Cliente=Cliente.objects.get(id='4'), Representada='3', Importe='100.00')
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "D:\Documents\Trabajo\00_Ofrevit\Desarrollo\Lib\site-packages\django\db\models\base.py", line 543, in __init__
_setattr(self, field.name, rel_obj)
File "D:\Documents\Trabajo\00_Ofrevit\Desarrollo\Lib\site-packages\django\db\models\fields\related_descriptors.py", line 284, in __set__
raise ValueError(
ValueError: Cannot assign "'3'": "Prevision.Representada" must be a "Representada" instance.
I guess the issue must be pretty obvious for someone with a bit of experience and it should be some how related to the fact that Prevision has a uniqueConstraint to keep a pseudo-multiple field PK apart from the id key automatically inserted by Django, but I just cannot see where or why... Any advice will be much appreciated!
Look at your pre_save
signal function:
@receiver(pre_save, sender=Prevision)
def comprobar_ano_prevision(sender, instance, **kwargs):
representada = Representada.objects.get(id=instance.Representada)
...
First trying to assign an instance as ID and later trying to access an instance field from an int (the error messages), so:
Representada.objects.get(id=instance.Representada.id)