When referencing imported Django model, I get 'local variable referenced before assignment' error

I am trying to import a model into my Django view and then query all objects, sort them, and iterate over them. I am not getting any error when importing the model, however, when trying to query the model with songs = song.objects.all()#.order_by('-release_date'), I am getting an error:

UnboundLocalError at /hotline/dbm
local variable 'song' referenced before assignment
/home/path/to/site/views.py, line 82, in dbm
songs = song.objects.all()#.order_by('-release_date')

I do not understand what the problem is, as the variable song is clearly imported from my models.py file, and I am not getting any errors importing it - so why is Python not recognizing song as what I imported from my models.py file?

My models.py file:

class song(models.Model):
    name = models.TextField()
    file = models.FileField()
    release_date = models.DateTimeField(default=timezone.now)

    class Meta:
        verbose_name = 'Song'
        verbose_name_plural = f'{verbose_name}s'

my views.py file:

#list of modules removed to keep code clean
from .models import *

@csrf_exempt
def dbm(request: HttpRequest) -> HttpResponse:
    songs = song.objects.all()#.order_by('-release_date')

    response = request.POST.get('Digits')

    if response == None:
        vr = VoiceResponse()

        vr.say("Please choose a song, and then press pound")
        vr.pause(length=1)

        with vr.gather(finish_on_key='#', timeout=6, numDigits="1") as gather:
            for song, num in songs:
                gather.pause(length=1)
                gather.say(f"For {song.name}, please press {num}")

        vr.redirect(reverse('dbm'))

        return HttpResponse(str(vr), content_type='text/xml')

    elif response != None:
        vr = VoiceResponse()
        vr.say("hi")

        return HttpResponse(str(vr), content_type='text/xml')

Thanks!

I do not understand what the problem is, as the variable song is clearly imported from my models.py file

Yes, but in your function you also work with a local variable named song, indeed:

for song, num in songs:
  # …

That means Python says that song is a local variable, and it thus refuses to look for the one outside the function.

But I think the main problem is that classes are typically written in PascalCase, not snake_case, so you probably better rename the model Song, not song. Since local variables are given snake_case names, this avoids clashes, so:

#      🖟 not song
class Song(models.Model):
    name = models.TextField()
    file = models.FileField()
    release_date = models.DateTimeField(default=timezone.now)

    class Meta:
        verbose_name = 'Song'
        verbose_name_plural = f'{verbose_name}s'

then the view looks like:

from .models import Song


@csrf_exempt
def dbm(request: HttpRequest) -> HttpResponse:
    songs = Song.objects..order_by('-release_date')

    response = request.POST.get('Digits')

    if response is None:
        vr = VoiceResponse()

        vr.say('Please choose a song, and then press pound')
        vr.pause(length=1)

        with vr.gather(finish_on_key='#', timeout=6, numDigits="1") as gather:
            for song, num in songs:
                gather.pause(length=1)
                gather.say(f"For {song.name}, please press {num}")

        vr.redirect(reverse('dbm'))

        return HttpResponse(str(vr), content_type='text/xml')

    else:
        vr = VoiceResponse()
        vr.say('hi')

        return HttpResponse(str(vr), content_type='text/xml')

Note: Please do not use wildcard imports [quantifiedcode.com]. It makes the statement less predictable, it can easily result in failing code if you later decide to change what is exported in a certain module, and furthermore it can override variables.

in your code, the lowercase song conflicts with the class name.

when you use:

from .models import *

django imports the song class and assigns it to a local name song.

for song, num in songs:

However, the interpreter assumes song in your function refers to a local variable (not the model class) because the for loop temporarily creates a local variable

and in Django, model classes should follow the PascalCase naming convention.

models.py:

class Song(models.Model):
    name = models.TextField()
    file = models.FileField()
    release_date = models.DateTimeField(default=timezone.now)

views.py

from .models import Song

@csrf_exempt
def dbm(request: HttpRequest) -> HttpResponse:
    songs = Song.objects.all()#.order_by('-release_date')
Вернуться на верх