NOT NULL constraint field issue

Hello everyone !

I’m trying to create a little blog app but when I try to create a new post from my home page I get a NOT NULL constraint field issue.

Here are my sources :

models.py

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

    class PostModel(models.Model):
    post_author = models.ForeignKey(User,related_name="post_author",on_delete=models.CASCADE)
    post_message = models.CharField(max_length=140)
    post_date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.post_message

views.py

    from django.shortcuts import render
    from .models import PostModel

    def new(request):
    if request.method == "POST":
        new_post_author = request.POST.get("post_author")
        new_post_message = request.POST.get("post_message")

        post = PostModel(post_author=new_post_author,post_message=new_post_message)
        post.save()      

    return redirect("site_base:home")

home.html

    {% if user.is_authenticated %}
    <a id="new_post_button">New Post</a>

    <form id="new_post_form" action="{% url 'posts:new' %}" method="POST">
    {% csrf_token %}
    <label id="post_author" type="text">{{ user.username }}</label>
    <textarea id="post_message" rows="10" cols="40" maxlength="140">Type in your post</textarea>
    <input type="submit" value="Submit Post">
    </form>
    {% endif %}

Thank you in advance for your help

The thing you get from your request here is likely a string. You’ll need to use that string to do an Author.objects.get to get an actual object to pass in.

Hi @alper !

Thank you for your answer, I’ve updated my code :

    from django.shortcuts import render
    from .models import PostModel
    from django.contrib.auth.models import User

    def new(request):
    if request.method == "POST":
        request_post_author = request.POST.get("post_author")
        new_post_author = User.objects.get(request_post_author.id)
        new_post_message = request.POST.get("post_message")

        post = PostModel(post_author=new_post_author,post_message=new_post_message)
        post.save()      

    return redirect("site_base:home")

But I now get :

'NoneType' object has no attribute 'id'

Whereas I’m logged in…

Post your traceback (always)

Here it is :

Environment:


Request Method: POST
Request URL: http://localhost:8000/posts/new

Django Version: 3.0.5
Python Version: 3.8.2
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'site_base.apps.SiteBaseConfig',
 'accounts.apps.AccountsConfig',
 'posts.apps.PostsConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "C:\Users\Théo\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
    response = get_response(request)
  File "C:\Users\Théo\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "C:\Users\Théo\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Théo\Web\blog\posts\views.py", line 8, in new
    new_post_author = User.objects.get(request_post_author.id)

Exception Type: AttributeError at /posts/new
Exception Value: 'NoneType' object has no attribute 'id'

I don’t think this is anything: request_post_author.id. If the thing coming in is a string, you can’t do .id on it.

Other than that the get call should do User.objects.get(pk=author_id)

You’re right, I should get the User object with the username string like so :

    request_post_author = request.POST.get("post_author")
    new_post_author = User.objects.get(username=request_post_author)

But this time I get :

Exception Type: DoesNotExist at /posts/new
Exception Value: User matching query does not exist.

When you get an error like that, usually the error is right. The User does not exist. Check what is going into the query and whether you can reproduce that in the shell.

Hi!

I noticed a couple of things:

From your first post

    <form id="new_post_form" action="{% url 'posts:new' %}" method="POST">
    {% csrf_token %}
    <label id="post_author" type="text">{{ user.username }}</label>
    <textarea id="post_message" rows="10" cols="40" maxlength="140">Type in your post</textarea>
    <input type="submit" value="Submit Post">
    </form>

I think that when you are using the <label> tag here, you actually mean to use something else, maybe an <input> tag? (and in that case maybe with a default attribute set to {{ user.username }} ). You can read about what the <label> tag does here: https://www.w3schools.com/tags/tag_label.asp This is what is causing the error 'NoneType' object has no attribute 'id', I believe.

Once you’ve fixed this, you should also change this part:

 if request.method == "POST":
        request_post_author = request.POST.get("post_author")
        new_post_author = User.objects.get(request_post_author.id)

I think you instead need to use

 if request.method == "POST":
        request_post_author = request.POST.get("post_author")
        new_post_author = User.objects.get(username=request_post_author)

Also, I think that the overall approach you’ve used might not be the most appropriate for what you want to achieve.

  • If you want the user to be able to specify not only him/herself, but any of the registered users, as the author, you probably want to use a Form class whose instances can fetch the usernames of currently existing users from the database, and create a single-choice list of usernames to select from. (or you could add code in your view to query the database, and add tags in your template, but I think at that point using a Form might be better) Otherwise, the user might try to input a username that doesn’t exist in the database, producing an error.
  • If you always want the current user to be registered as the author of the post, I’m guessing you would be better off not including anything about that in the form, and instead using request.user in your view to get information (e. g. the pk/id) about the current user. If I’ve understood how that can be used correctly that is, I haven’t used it much myself but here’s a relevant link to the django docs https://docs.djangoproject.com/en/3.0/topics/auth/default/#s-authentication-in-web-requests

Hope this helps :slight_smile:

Hello everyone ! Thanks a lot for your advices !

I tried to do it manually through the shell :

    >>> from django.contrib.auth.models import User
    >>> from posts.models import PostModel
    >>> new_post_author = User.objects.get(username="pepito8")
    >>> new_post_message = "Hi ! I'm Pepito !"
    >>> new_post = PostModel(post_author=new_post_author,post_message=new_post_message)
    >>> print(new_post)
    Hi ! I'm Pepito !
    >>> new_post.save()

It works perfectly so the issue comes from my new view… or my HTML code… I don’t know how to approach this issue :frowning:

I think you’ll find things will work a whole lot easier if you create your page using a Django form rather than rendering the fields manually and trying to process the POST elements yourself. That’s one of the real benefits of working with a framework like Django - it does a lot of the “housekeeping” that you would otherwise need to do yourself.

Ken

Hi @KenWhitesell ! What do you mean exactly ? Rendering the whole page through a form ?

I just tried to get more details through the print function, here is what I got :

request.POST => <QueryDict: {'csrfmiddlewaretoken': ['crQnZSzME61NEzT6qXkx5DDF5xP2fEl9fYNjOQMSfzBebJBXnVXNZcWeOM3679x7']}> request_post_author => None

The thing is that the form is not sending the whole data, just the csrf_token, that’s why it doesn’t work.

    {% if user.is_authenticated %}
    <a id="new_post_button">New Post</a>

    <form id="new_post_form" action="{% url 'posts:new' %}" method="POST">
    {% csrf_token %}
    <label id="post_author" type="text">{{ user.username }}</label>
    <textarea id="post_message" rows="10" cols="40" maxlength="140">Type in your post</textarea>
    <input type="submit" value="Submit Post">
    </form>
    {% endif %}

When in doubt, review the appropriate documentation.

Briefly -

  • Create a form class

  • Create a template that renders the form as part of the template

  • Create a view that renders the template on a GET, and saves the data from the form on a POST

(Or, better yet, start getting familiar with the generic Class-Based Views and implement this page as a DetailView. Or, take a look at the django vanilla views package.)

Ken

Actually, you’ve worked with forms before - Update/Edit database content with form - it’s basically the same thing here.

Hi @KenWhitesell ! You’re right !

I totally forgot this approach as I was too focused on the “Login/Authentication” aspect of my exercise.

Here is the new HTML code :

    {% if user.is_authenticated %}
    <a id="new_post_button">New Post</a>

    <form id="new_post_form" action="{% url 'posts:new' %}" method="POST">
    {% csrf_token %}
    {{ PostForm.post_author }}
    {{ PostForm.post_message }}
    <input type="submit" value="Submit Post">
    </form>
    {% endif %} 

Now the data is indeed sent to the request.POST attribute as a dictionnary.

And the forms.py file :

    from django.forms import ModelForm,Textarea
    from .models import PostModel

    class PostForm(ModelForm):
    class Meta:
        model = PostModel
        fields = "__all__"
        widgets = {
            "post_message": Textarea(attrs={
                "rows":10,
                "cols":40,
                "maxlength":140,
            }),
        }

My last issue here is that :

{{ PostForm.post_author }}

Will give me an HTML list of every User objects present in my db. What I’d like is to say Hey I want you to automatically choose the current logged in user to be the post_author

How can I do that ?

EDIT :

As @datalowe said above (Thank you by the way :wink: ), one can get the logged in user by using request.user like this :

    def new(request):
    if request.method == "POST":
        request_post_author = request.user
        new_post_author = User.objects.get(username=request_post_author)
        new_post_message = request.POST.get("post_message")

        post = PostModel(post_author=new_post_author,post_message=new_post_message)
        post.save()      

    return redirect("site_base:home")