Я пытаюсь создать свой первый проект в Django, и у меня возникают некоторые проблемы при создании записи в моей модели Prevision (включая прогнозы продаж), которая имеет уникальное ограничение, гарантирующее, что между поставщиком (моделью) существует только один прогноз. «Representada») и Заказчик (модель «Cliente») на указанный год (поле «Ano»).
Мой файл models.py выглядит следующим образом:
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")
Как только модели будут правильно перенесены, я вхожу в режим администратора и без проблем регистрирую поставщика (т. е. «Representada») и клиента (т. е. «Cliente»). Как только я зарегистрировал по одному из каждого из них в базе данных, я пытаюсь сделать то же самое с прогнозом (т. е. «Предварительное видение»), однако при попытке сделать это через режим администратора в веб-браузере я получил ошибку сервера (500) в возвращаться.
Пытаясь лучше понять, что происходит, я запускаю оболочку, чтобы поближе рассмотреть ошибку:
(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>.
>>>
Если я попытаюсь использовать число вместо экземпляра, как предлагает TypeError, дела пойдут не лучше:
>>> 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.
Я предполагаю, что проблема должна быть довольно очевидна для кого-то с небольшим опытом, и она должна быть каким-то образом связана с тем фактом, что Prevision имеет уникальное ограничение, позволяющее хранить псевдомножественное поле PK отдельно от ключа идентификатора, автоматически вставленного Django, но я просто не могу понять, где и почему... Любой совет будет очень признателен!
Посмотрите на свою сигнальную функцию pre_save
:
@receiver(pre_save, sender=Prevision)
def comprobar_ano_prevision(sender, instance, **kwargs):
representada = Representada.objects.get(id=instance.Representada)
...
Сначала пытаюсь назначить экземпляр в качестве идентификатора, а затем пытаюсь получить доступ к полю экземпляра из целого числа (сообщения об ошибках), поэтому:
Representada.objects.get(id=instance.Representada.id)
не используйте заглавные буквы в именах полей. Не обязательно, но это облегчит вашу жизнь, так вы узнаете, что используете класс или экземпляр (или переменную).
согласно первой трассировке, проблема заключается в этой строке:
# Desarrollo\Company\BaseDatos\models.py", line 290
representada = Representada.objects.get(id=instance.Representada)
потому что instance.Representada
— это экземпляр, а не целое число. Вместо этого передайте pk:
# Desarrollo\Company\BaseDatos\models.py", line 290
representada = Representada.objects.get(id=instance.Representada.pk)
это должно исправить ошибку, но...
instance.Representada
уже является экземпляром модели Representada (понимаете, почему важен пункт 1?), вы можете просто сделать:representada = instance.representada
С этого момента я следую вашему совету, спасибо за совет!
Спасибо за быстрый ответ, Нико. Это сработало!