You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django诊所应用创建预约收入接口时出现AttributeError: 'User'对象无'user'属性的问题求助

Django诊所应用创建预约收入接口时出现AttributeError: 'User'对象无'user'属性的问题求助

大家好,我正在开发一款诊所APP的财务模块,想通过预约创建收入记录,但用Postman测试接口时遇到了如下错误:

AttributeError: Got AttributeError when attempting to get a value for field user on serializer UserInfoSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the User instance.
Original exception text was: 'User' object has no attribute 'user'.

这个问题已经卡了我好几天了,实在找不到解决办法,我把相关的代码文件贴出来,麻烦大家帮我看看问题出在哪!

userinfo/models.py

from django.db import models
from django.contrib.auth.models import User

class UserInfo(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="userInfo")
    address = models.CharField(max_length=255, blank=True, null=True)
    phone = models.CharField(max_length=15, blank=True, null=True)
    fecha_nacimiento = models.DateField(blank=True, null=True)
    dni = models.CharField(max_length=10, blank=True, null=True)
    postal_code = models.CharField(max_length=6, blank=True, null=True)
    city = models.CharField(max_length=200, blank=True, null=True)
    country = models.CharField(max_length=100, blank=True, null=True)
    segundo_apellido = models.CharField(max_length=200, blank=True)

    def __str__(self):
        return f"Información de {self.user.username}"

finanzas/models.py

from django.db import models
from citas.models import Citas
from userinfo.models import UserInfo

class Transaccion(models.Model):
    INGRESO = 'INGRESO'
    GASTO = 'GASTO'
    INGRESO_COTIZADO = 'INGRESO_COTIZADO'

    TIPO_CHOICES = [
        (INGRESO, 'Ingreso'),
        (GASTO, 'Gasto'),
        (INGRESO_COTIZADO, 'Ingreso_Cotizado')
    ]

    tipo = models.CharField(
        max_length=100,
        choices=TIPO_CHOICES,
        default=INGRESO,
    )
    monto = models.DecimalField(max_digits=10, decimal_places=2)
    descripcion = models.TextField()
    fecha = models.DateTimeField(auto_now_add=True)
    cita = models.ForeignKey(Citas, on_delete=models.CASCADE)  # Relacionado con la cita
    user = models.ForeignKey(UserInfo, on_delete=models.CASCADE)  # Relacionado con el usuario que cotiza
    url = models.URLField(null=True, blank=True)

    def __str__(self):
        return f"Transacción de {self.tipo} por {self.monto} para la cita {self.cita}"

class ConfiguracionFinanzas(models.Model):
    precio_cita_base = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="Precio base de la cita")
    ultima_actualizacion = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"Configuración de Finanzas (Precio Cita: {self.precio_cita_base})"

views.py

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from finanzas.models import Transaccion, ConfiguracionFinanzas
from citas.models import Citas
from django.db.models import Sum
from .serializers import TransaccionSerializer, ConfiguracionFinanzasSerializer
from rest_framework.exceptions import NotFound
from datetime import datetime, timedelta
from django.utils import timezone

def obtener_transacciones_por_periodo(filtro, usuario_info):
    hoy = timezone.now()
    transacciones = Transaccion.objects.filter(usuario=usuario_info)

    if filtro == 'mensual':
        inicio_mes = hoy.replace(day=1)
        transacciones = transacciones.filter(fecha__gte=inicio_mes)
    elif filtro == 'trimestral':
        inicio_trimestre = hoy - timedelta(days=90)
        transacciones = transacciones.filter(fecha__gte=inicio_trimestre)
    elif filtro == 'anual':
        inicio_anio = hoy.replace(month=1, day=1)
        transacciones = transacciones.filter(fecha__gte=inicio_anio)

    return transacciones

def obtener_balance_por_periodo(tipo, filtro, usuario_info):
    hoy = timezone.now()
    transacciones = Transaccion.objects.filter(tipo=tipo, usuario=usuario_info)

    if filtro == 'mensual':
        inicio_mes = hoy.replace(day=1)
        transacciones = transacciones.filter(fecha__gte=inicio_mes)
    elif filtro == 'trimestral':
        inicio_trimestre = hoy - timedelta(days=90)
        transacciones = transacciones.filter(fecha__gte=inicio_trimestre)
    elif filtro == 'anual':
        inicio_anio = hoy.replace(month=1, day=1)
        transacciones = transacciones.filter(fecha__gte=inicio_anio)

    return transacciones.aggregate(Sum('monto'))['monto__sum'] or 0

class ListarGananciasCitasView(APIView):
    """
    Listar las transacciones de ganancias por citas.
    """
    def get(self, request):
        filtro = request.query_params.get('filtro', 'total')
        usuario_info = request.user.userInfo
        transacciones = obtener_transacciones_por_periodo(filtro, usuario_info)

        serializer = TransaccionSerializer(transacciones, many=True)
        return Response(serializer.data)

class CrearGananciasCitasView(APIView):
    """
    Vista para crear una transacción de tipo 'Ingreso' o 'Gasto'.
    Asocia la transacción a una cita y al usuario que la cotiza.
    """
    def post(self, request, cita_id):
        if not request.user.is_authenticated:
            return Response({"detail": "Usuario no autenticado."}, status=status.HTTP_401_UNAUTHORIZED)

        try:
            cita = Citas.objects.get(id=cita_id)
        except Citas.DoesNotExist:
            raise NotFound("Cita no encontrada.")

        if cita.user != request.user:
            return Response({"detail": "No puedes modificar una cita de otro usuario."}, status=status.HTTP_403_FORBIDDEN)

        monto = request.data.get('monto')
        descripcion = request.data.get('descripcion')
        if monto is None or descripcion is None:
            return Response({"detail": "Se requieren los campos 'monto' y 'descripcion'."}, status=status.HTTP_400_BAD_REQUEST)

        tipo = 'INGRESO' if not cita.cotizada else 'INGRESO_COTIZADO'

        usuario_info = getattr(request.user, 'userInfo', None)
        if usuario_info is None:
            return Response({"detail": "Información del usuario no encontrada."}, status=status.HTTP_404_NOT_FOUND)
        transaccion = Transaccion.objects.create(
            tipo=tipo,
            monto=monto,
            descripcion=descripcion,
            cita=cita,
            user=usuario_info,
        )

        serializer = TransaccionSerializer(transaccion)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

class MarcarCitaCotizadaView(APIView):
    """
    Vista para marcar una cita como cotizada
    """
    def post(self, request, cita_id):
        if not request.user.is_authenticated:
            return Response({"detail": "Usuario no autenticado."}, status=status.HTTP_401_UNAUTHORIZED)

        try:
            cita = Citas.objects.get(id=cita_id)
        except Citas.DoesNotExist:
            raise NotFound("Cita no encontrada.")

        if cita.usuario != request.user.userInfo:
            return Response({"detail": "No puedes modificar una cita de otro usuario."}, status=status.HTTP_403_FORBIDDEN)

        cita.cotizada = True
        cita.save()

        return Response({"message": "Cita marcada como cotizada"}, status=status.HTTP_200_OK)

class CrearGastoView(APIView):
    """
    Vista para crear un gasto independiente de las citas.
    """
    def post(self, request):
        if not request.user.is_authenticated:
            return Response({"detail": "Usuario no autenticado."}, status=status.HTTP_401_UNAUTHORIZED)

        monto = request.data.get('monto')
        descripcion = request.data.get('descripcion')
        if monto is None or descripcion is None:
            return Response({"detail": "Se requieren los campos 'monto' y 'descripcion'."}, status=status.HTTP_400_BAD_REQUEST)

        tipo = "GASTO"
        url = request.data.get('url', None)

        usuario_info = request.user.userInfo
        gasto = Transaccion.objects.create(
            tipo=tipo,
            monto=monto,
            descripcion=descripcion,
            usuario=usuario_info,
            url=url
        )

        serializer = TransaccionSerializer(gasto)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

class ListarGastosView(APIView):
    """
    Listar los gastos del usuario autenticado
    """
    def get(self, request):
        filtro = request.query_params.get('filtro', 'total')
        usuario_info = request.user.userInfo
        gastos = obtener_transacciones_por_periodo(filtro, usuario_info)

        serializer = TransaccionSerializer(gastos, many=True)
        return Response(serializer.data)

class ConfiguracionFinanzasView(APIView):
    """
    Obtener y actualizar la configuración de finanzas.
    """
    def get(self, request):
        configuracion = ConfiguracionFinanzas.objects.first()
        if not configuracion:
            configuracion = ConfiguracionFinanzas.objects.create(precio_cita_base=0)
        serializer = ConfiguracionFinanzasSerializer(configuracion)
        return Response(serializer.data)

    def put(self, request):
        configuracion = ConfiguracionFinanzas.objects.first()
        if not configuracion:
            configuracion = ConfiguracionFinanzas.objects.create(precio_cita_base=0)
        serializer = ConfiguracionFinanzasSerializer(configuracion, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class FinanzasBalanceView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        usuario_info = request.user.userInfo

        # Obtención de los balances por periodo
        ingresos_totales = obtener_balance_por_periodo('INGRESO', 'total', usuario_info)
        ingresos_cotizados_totales = obtener_balance_por_periodo('INGRESO_COTIZADO', 'total', us

备注:内容来源于stack exchange,提问作者Rcacodedev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:23:20