Search code examples
pythondjangodjango-modelsdjango-admindjango-migrations

Changing db_table on Django doesn't work after migrations, and adding a user (AbstractUser) foreign key breaks __str__()


This is a 2 problem post, which i suspect are somehow connected. My posts app on Django doesn't migrate properly (I think), and adding the 'author' (Users AbstractUser model) breaks my code with the error:

str returned non-string (type tuple)

whenever I try to add a row.

I changed the name of the table using db_table (so it will change Postss to Posts on the admin page) and added an output to the __str__ function, I tried a few variations of output for the str function but non of them worked. I even tried to comment it out. and still. no changes in the table name or the error. I tried changing my str functions a few times and got the same result. when deleting the str function, I still get that same result.

I have a few wild guesses about this situation:

  • it might not work with an abstract users class for some reason, and do not know to to work around this and why this happens at all. (removeing the author=... row resolves this problem but doesn't change the table name, but I do need the user fk there).
  • it might not be migrating properly due to my environment or python version Python 3.9.1(?).
  • PS: every change I have made included stopping the server from running and running the lines: python manage.py migrate and python manage.py makemigrations.

posts/models.py:

from django.db import models
from users.models import Users


class Posts(models.Model):
    
    content = models.TextField(max_length=255)
    author = models.ForeignKey('users.Users', on_delete=models.CASCADE, null=True)
    timestamp = models.DateTimeField(auto_now_add=True, null=True)
    
    class Meta:
        db_table = 'Posts'


    def __str__(self):
        return self.content + " " + self.author.email + " " + self.timestamp

I will add my users model too:

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


# https://rahmanfadhil.com/django-login-with-email/
class Users(AbstractUser): 

    USERNAME_FIELD = 'email'
    email = models.EmailField(unique=True)
    REQUIRED_FIELDS = ['username'] # removes email from REQUIRED_FIELDS

    def __str__(self):
        return (self.email, self.username)

enter image description here


Solution

  • You are trying return a tuple, it must be a string.

    def str(self): return (self.email, self.username)

    Use the new-style formatting in Python

    Try this instead:

    class Users(AbstractUser):
        ...
        
        def __str__(self):
            template = '{0.email} {0.username}'
            return template.format(self)
    
    
    class Posts(models.Model):
        ...
    
        def __str__(self):
            template = '{0.content} {0.author.email} {0.timestamp}'
            return template.format(self)
    

    or

    def __str__(self):
        return '{} {} {}'.format(self.content, self.author.email, self.timestamp)
    

    Second Question:

    If I understand your question correctly, then to change the label for the model, you need to use verbose_name:

    class Meta:
        verbose_name = 'Post'
        verbose_name_plural = 'Posts'
    

    With option db_table you change the name of the database table to use for the model.

    And see explanation how it works, from Django: if you have an app bookstore (as created by manage.py startapp bookstore), a model defined as class Book will have a database table named bookstore_book.

    Then db_table must be:

    class Meta:
        # your desired table name can you find in your database
        db_table = 'appLabel_modelName'
    

    To list the tables in the current database for PostgreSQL, you can run:

    python manage.py dbshell
    
    \dt
    

    See also Django db_table