MultipleObjectsReturned на /editar-acumulado/2/ get() вернуло больше одного Acumulado — он вернул 3

Вопрос или проблема

В моем проекте Django есть следующий код:

@login_required
def editar_acumulado(request, estudiante_id):
    estudiante = Estudiante.objects.get(id=estudiante_id)
    grupo_clase = estudiante.matriculas.first().grupoclase
    asignatura = grupo_clase.asignatura  # Получить предмет, зарегистрированный в классе студента

    try:
        acumulado = Acumulado.objects.get(estudiante=estudiante, asignatura=asignatura)
    except Acumulado.DoesNotExist:
        messages.warning(request, 'Вы должны зарегистрировать накопленные баллы в "0" для этого студента, прежде чем регистрировать его накопленные баллы.')
        return redirect('RegistroCalificaciones', grupo_clase_id=grupo_clase.id)

    if request.method == 'POST':
        form = EditAcumuladoForm(request.POST, instance=acumulado)
        if form.is_valid():
            form.save()
            messages.success(request, 'Накопленные баллы успешно обновлены')
            return redirect('RegistroCalificaciones', grupo_clase_id=grupo_clase.id)
    else:
        form = EditAcumuladoForm(instance=acumulado)

    return render(request, 'PanelDocentes/PortalNotas.html', {'form': form, 'estudiante': estudiante})

также оставлю здесь код своей формы:

class EditAcumuladoForm(forms.ModelForm):
    class Meta:
        model = Acumulado
        fields = ('parcial', 'asignatura', 'semestre', 'AnioEscol', 'Nota1', 'Nota2', 'Nota3', 'Nota4', 'Nota5', 'Examen')

    def __init__(self, *args, **kwargs):
        estudiante = kwargs.pop('estudiante', None)
        super().__init__(*args, **kwargs)

        if estudiante:
            matricula_grupo = estudiante.matriculas.first()
            grupo_clase = matricula_grupo.grupoclase
            self.fields['asignatura'].queryset = Asignatura.objects.filter(id=grupo_clase.asignatura.id)

        self.fields['parcial'].widget.attrs['class'] = 'form-control'
        self._set_attributes('Nota1', 'Nota2', 'Nota3', 'Nota4', 'Nota5', min_value=0, max_value=20)
        self._set_attributes('Examen', min_value=0, max_value=40)

        self.fields['semestre'] = forms.ModelChoiceField(queryset=Semestre.objects.all(), widget=forms.Select(attrs={'class': 'form-control'}))
        self.fields['AnioEscol'] = forms.ModelChoiceField(queryset=AnioEscolar.objects.all(), widget=forms.Select(attrs={'class': 'form-control'}))

        self.fields['suma_notas'] = forms.CharField(label="Итоговые накопленные баллы", widget=forms.TextInput(attrs={'readonly': 'readonly', 'class': 'form-control'}))
        self.fields['suma_total'] = forms.CharField(label="Итоговая оценка", widget=forms.TextInput(attrs={'readonly': 'readonly', 'class': 'form-control'}))

        instance = kwargs.get('instance')
        if instance:
            Nota1 = instance.Nota1 if instance.Nota1 is not None else 0
            Nota2 = instance.Nota2 if instance.Nota2 is not None else 0
            Nota3 = instance.Nota3 if instance.Nota3 is not None else 0
            Nota4 = instance.Nota4 if instance.Nota4 is not None else 0
            Nota5 = instance.Nota5 if instance.Nota5 is not None else 0
            Examen = instance.Examen if instance.Examen is not None else 0

            suma_notas = Nota1 + Nota2 + Nota3 + Nota4 + Nota5
            suma_total = suma_notas + Examen

            self.fields['suma_notas'] = forms.CharField(label="Итоговые накопленные баллы", initial=suma_notas, widget=forms.TextInput(attrs={'readonly': 'readonly', 'class':'form-control'}))
            self.fields['suma_total'] = forms.CharField(label="Итоговая оценка", initial=suma_total, widget=forms.TextInput(attrs={'readonly': 'readonly', 'class':'form-control'}))

            if suma_total == 60:
                for field_name in ['Nota1', 'Nota2', 'Nota3', 'Nota4', 'Nota5']:
                    field = self.fields[field_name]
                    if field.initial is None:
                        field.widget.attrs['disabled'] = True
                        field.widget.attrs['class'] += ' disabled'

    def _set_attributes(self, *fields, min_value=None, max_value=None):
        for field in fields:
            self.fields[field].widget.attrs['class'] = 'form-control'
            if min_value is not None:
                self.fields[field].widget.attrs['min'] = min_value
            if max_value is not None:
                self.fields[field].widget.attrs['max'] = max_value

models.py:

from django.db import models
from django.contrib.auth.models import AbstractUser
from datetime import date

#=======================ТАБЛИЦА СТАТУСА ПОЛЬЗОВАТЕЛЯ=======================
class Estado(models.Model):
    estado = [
        ('Activo','Activo'), 
        ('Inactivo', 'Inactivo'), 
    ]
    Estados = models.CharField(max_length=10, choices=estado, blank=True, null=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.Estados}"

    class Meta:
        verbose_name_plural = "Estados"

#=======================ТАБЛИЦА РАЗДЕЛ=======================
class Seccion(models.Model):
    secciones = [
        ('A', 'A'),
        ('B', 'B'),
        ('C', 'C'),
        ('D', 'D'),
    ]
    SeccionNivel = models.CharField(max_length=1, choices=secciones, blank=True, null=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.SeccionNivel}"

    class Meta:
        verbose_name_plural = "Secciones"

#=======================ТАБЛИЦА КЛАССОВ=======================
class Grado(models.Model):
    CodigoGrado = models.CharField(max_length=4)
    grados = [
        ('1ro', '1ro'), ('2do', '2do'), ('3ro', '3ro'),
        ('4to', '4to'), ('5to', '5to'), ('6to', '6to'),
        ('7mo', '7mo'), ('8vo', '8vo'), ('9no', '9no'),
        ('10mo','10mo'), ('11mo','11mo')

    ]
    GradoNivel = models.CharField(max_length=4, choices=grados, blank=True, null=True)
    seccion = models.ForeignKey(Seccion, on_delete=models.PROTECT, blank=True,null=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.CodigoGrado}, {self.GradoNivel}"

    class Meta:
        verbose_name_plural = "Grados"

#=======================ТАБЛИЦА ПРЕДМЕТОВ===================
class Asignatura(models.Model):
    CodigoAsigantura = models.CharField(max_length=6, default="codigo")
    nombreAsignatura = [
        ('Lengua y Literatura', 'Lengua y Literatura'), 
        ('Lengua Extranjera','Lengua Extranjera'),
        ('Creciendo en Valores','Creciendo en Valores'),
        ('Conociendo mi Mundo','Conociendo mi Mundo'),
        ('Derecho y Dignidad de la Mujer','Derecho y Дignidad de la Mujer'),
        ('Educación Física','Educación Física'),
        ('Aprender Emprender y Prosperar','Aprender Emprender y Prosperar'),
        ('Estudios Sociales','Estudios Sociales'),
        ('Talleres de Arte y Cultura','Talleres de Arte y Cultura'),
        ('Ciencias Naturales','Ciencias Naturales'),
        ('Biología','Biología'),
        ('Matemática','Matemática'), 
        ('Física','Física'),
        ('Química','Química'),
        ('Computación','Computación')

    ]
    asignatura = models.CharField(max_length=100, choices=nombreAsignatura, blank=True, null=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.CodigoAsigantura}, {self.asignatura}"

    class Meta:
        verbose_name_plural = "Asignaturas"

#=======================ТАБЛИЦА СТУДЕНТОВ=======================
class Estudiante(models.Model):
    Nombres = models.CharField(max_length=50)
    Apellidos = models.CharField(max_length=50)
    CodigoEstudiantil = models.CharField(max_length=25)
    estado = models.ForeignKey(Estado, on_delete=models.PROTECT)
    FechaRegistro = models.DateTimeField(auto_now_add=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.Nombres} {self.Apellidos}"

    class Meta:
        verbose_name_plural = "Estudiantes"

#=======================ТАБЛИЦА УЧЕБНОГО ГОДА===================
class AnioEscolar(models.Model):
    anio = models.CharField(max_length=5, default=date.today().year)
    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.anio}"

    class Meta:
        verbose_name_plural = "Año Escolar"

#=======================ТАБЛИЦА ПАРЦИАЛЬНЫХ ОЦЕНОК=======================
class Parciales(models.Model):
    parciales = [
        ('I Parcial','I Parcial'),
        ('II Parcial','II Parcial'),
        ('III Parcial','III Parcial'),
        ('IV Parcial','IV Parcial')
    ]
    parcial = models.CharField(max_length=20, choices=parciales, blank=True, null=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.parcial}"

    class Meta:
        verbose_name_plural = "Parciales"

class Semestre(models.Model):
    CodigoSemestre = models.CharField(max_length=3, default="semestre")
    sem = [
        ('I Semestre','I Semestre'),
        ('II Semestre','II Semestre')
    ]
    semestreNivel = models.CharField(max_length=15, choices=sem, blank=True, null=True)
    parcial = models.ForeignKey(Parciales, on_delete=models.PROTECT, blank=True,null=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.CodigoSemestre}, {self.semestreNivel}"

    class Meta:
        verbose_name_plural = "Semestres"

#=======================ТАБЛИЦА НАКОПЛЕННЫХ ОЦЕНОК=======================
class Acumulado(models.Model):
    Nota1 = models.IntegerField(blank=True, null=True)
    Nota2 = models.IntegerField(blank=True, null=True)
    Nota3 = models.IntegerField(blank=True, null=True)
    Nota4 = models.IntegerField(blank=True, null=True)
    Nota5 = models.IntegerField(blank=True, null=True)
    Examen = models.IntegerField(blank=True, null=True)
    actividad = models.TextField()
    asignatura = models.ForeignKey(Asignatura, on_delete=models.PROTECT)
    estudiante = models.ForeignKey(Estudiante, on_delete=models.PROTECT)
    parcial = models.ForeignKey(Parciales, on_delete=models.PROTECT)
    semestre = models.ForeignKey(Semestre, on_delete=models.PROTECT)
    AnioEscol = models.ForeignKey(AnioEscolar, on_delete=models.PROTECT, default="")

     #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.estudiante}, {self.Nota1}, {self.Nota2}, {self.Nota3}, {self.Nota4}, {self.Nota5}, {self.Examen}, {self.asignatura.CodigoAsigantura}, {self.semestre.CodigoSemestre}, {self.parcial.parcial}"

    class Meta:
        verbose_name_plural = "Acumulados"

#=======================ТАБЛИЦА ОЦЕНОК=======================
class Nota(models.Model):
    NotaCualitativa = models.CharField(max_length=2)
    NotaCuantitativa = models.DecimalField(max_digits=5, decimal_places=2)
    Fecha = models.DateTimeField(auto_now_add=True)
    asignatura = models.ForeignKey(Asignatura, on_delete=models.PROTECT)
    estudiante = models.ForeignKey(Estudiante, on_delete=models.PROTECT)
    acumulado = models.ForeignKey(Acumulado, on_delete=models.PROTECT)
    parcial = models.ForeignKey(Parciales, on_delete=models.PROTECT)
    semestre = models.ForeignKey(Semestre, on_delete=models.PROTECT)

    class Meta:
        verbose_name_plural = "Notas"

#=======================ТАБЛИЦА ПОЛЬЗОВАТЕЛЕЙ=======================
class User(AbstractUser):
    CodigoUser = models.CharField(max_length=15, null=False)
    rol = [
        ('Administrador','Administrador'),
        ('Asistente','Asistente'),
        ('Docente','Docente')
    ]
    Rol = models.CharField(max_length=255, choices=rol, blank=True, null=True)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.first_name} {self.last_name}, {self.CodigoUser}"

    class Meta:
        verbose_name_plural = "Usuarios"

#=======================ТАБЛИЦА ГРУППЫ КЛАССОВ=======================
class GrupoClase(models.Model):
    CodigoGrupo = models.CharField(max_length=20,blank=True, null=True)
    NombreGrupo = models.CharField(max_length=50)
    FechaInicio = models.DateField(null=False)
    FechaFin = models.DateField(blank=True, null=True)
    grado = models.ForeignKey(Grado, on_delete=models.PROTECT)
    seccion = models.ForeignKey(Seccion, on_delete=models.PROTECT)
    docente = models.ForeignKey(User, on_delete=models.PROTECT)
    asignatura = models.ForeignKey(Asignatura, on_delete=models.PROTECT)
    anio = models.ForeignKey(AnioEscolar, on_delete=models.PROTECT)
    estado = models.ForeignKey(Estado, on_delete=models.PROTECT)
    semestre = models.ForeignKey(Semestre, on_delete=models.PROTECT)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.NombreGrupo}, {self.docente}, {self.asignatura}"

    class Meta:
        verbose_name_plural = "Grupos De Clases"

    def generate_codigo_grupo(self):
        return f"{self.grado.CodigoGrado}{self.seccion}-{self.asignatura.CodigoAsigantura}-{self.semestre.CodigoSemestre}{self.anio.anio}"

    def save(self, *args, **kwargs):
        # Всегда генерировать новый код при сохранении
        self.CodigoGrupo = self.generate_codigo_grupo()
        super().save(*args, **kwargs)

#=======================ТАБЛИЦА ЗАЧЕТ В ГРУППАХ=======================
class MatriculaGrupo(models.Model):
    grupoclase = models.ForeignKey(GrupoClase, on_delete=models.PROTECT)
    estudiante = models.ForeignKey(Estudiante, on_delete=models.PROTECT, related_name="matriculas")
    estado = models.ForeignKey(Estado, on_delete=models.PROTECT)

    #Показать свойства в админ-панели Django.
    def __str__(self):
        return f"{self.grupoclase}, {self.estudiante}"

    class Meta:
        verbose_name_plural = "Matriculas Grupos"

#=======================ТАБЛИЦА АКАДЕМИЧЕСКИХ БИЛЕТОВ=======================
class BoletaAcademica(models.Model):
    CodigoBoleta = models.CharField(max_length=20)
    NoBoleta = models.IntegerField()
    FechaBoleta = models.DateTimeField()
    grado = models.ForeignKey(Grado, on_delete=models.PROTECT)
    seccion = models.ForeignKey(Seccion, on_delete=models.PROTECT)
    asignatura = models.ForeignKey(Asignatura, on_delete=models.PROTECT)
    docente = models.ForeignKey(User, on_delete=models.PROTECT)
    nota = models.ForeignKey(Nota, on_delete=models.PROTECT)
    estudiante = models.ForeignKey(Estudiante, on_delete=models.PROTECT)
    semestre = models.ForeignKey(Semestre, on_delete=models.PROTECT)
    anio = models.ForeignKey(AnioEscolar, on_delete=models.PROTECT, default=date.today().year)

    class Meta:
        verbose_name_plural = "Boletas Académicas"

Это работает правильно, когда студент имеет оценки только по одному предмету в своей группе, но когда студент имеет оценки по разным предметам, я получаю ошибку.

Ошибка, которую я получаю, это MultipleObjectsReturned, которая возникает потому, что запрос Accumulated.objects.get(student=student, subject=subject) возвращает более одного объекта Accumulated. Это происходит потому, что существует несколько объектов Accumulated, которые соответствуют условию наличия одного и того же студента и предмета.

Ответ или решение

Ошибка MultipleObjectsReturned в Django возникает, когда запрос, ожидающий вернуть один объект, фактически возвращает несколько. В вашем случае это происходит из-за того, что запрос Acumulado.objects.get(estudiante=estudiante, asignatura=asignatura) возвращает несколько объектов Acumulado, соответствующих одному и тому же студенту и предмету.

Причины возникновения ошибки

Ваша модель Acumulado описывает информацию о накопленных оценках для студентов, и у вас может быть несколько записей для одного и того же студента по одной и той же предмету (например, в разных семестрах или на различных уровнях). Это является причиной того, что метод .get() не может однозначно идентифицировать один объект и вместо этого возвращает несколько записей.

Возможные решения

Для решения этой проблемы вы можете рассмотреть несколько подходов:

  1. Использовать .filter() вместо .get():
    Если вам нужно работать со всеми записями Acumulado для данного студента и предмета, вы можете использовать filter():

    acumulados = Acumulado.objects.filter(estudiante=estudiante, asignatura=asignatura)
    if not acumulados.exists():
       messages.warning(request, 'Должны быть зарегистрированыAccumulated на "0" для этого студента перед регистрацией его накопленных оценок.')
       return redirect('RegistroCalificaciones', grupo_clase_id=grupo_clase.id)

    Далее, вы можете обрабатывать полученные записи в любом нужном вам формате (например, выбрать последнюю запись или работать с несколькими записями).

  2. Добавление уникальности в модель:
    Если логика вашего приложения подразумевает, что для каждого студента и каждого предмета может быть только одна запись, вы можете рассмотреть возможность добавления уникального ограничения в вашу модель Acumulado:

    class Acumulado(models.Model):
       ...
       class Meta:
           unique_together = ('estudiante', 'asignatura', 'semestre')  # Уникальность по студенту, предмету и семестру

    В результате при попытке создания нового Acumulado с одинаковыми значениями полей estudiante, asignatura и semestre будет возвращаться ошибка.

  3. Работа с дополнительными параметрами:
    Если ваши данные включают различные семестры или другие параметры, вам следует убедиться, что вы также учитываете эти параметры в своем запросе. Например, добавление semestre в вашу выборку:

    acumulado = Acumulado.objects.get(estudiante=estudiante, asignatura=asignatura, semestre=semestre)

    Это гарантирует, что вы получите уникальный объект на основе всех необходимых полей.

Заключение

Выбор подходящего решения зависит от ответов на следующие вопросы:

  • Необходимо ли вам хранить несколько записей для одного студента и предмета?
  • Если да, то как вы планируете обрабатывать эти записи в вашей логике приложения?
  • Есть ли необходимость в реализации уникальных ограничений для вашей модели?

Обеспечив правильную архитектуру данных, вы сможете избежать подобных ошибок в будущем и обеспечить стабильную работу вашего приложения.

Оцените материал
Добавить комментарий

Капча загружается...