Making a topic public to all users in Django

Viewed 404

I've got a project where users can enter topics and have the option to make the topic public or private. I want all public topics to be visible to everyone, and private topics to only be visible by the owner of that topic (Project is from Python Crash Course exercise 20-5). I'm not getting any errors when I click through my project, but when I create a new topic and select the "public" option, it is still not showing up for public viewing.

I've researched similar problems and followed all the suggestions here How to make a topic public to all users in django? and here Django - How to make the topics, that you create public? Learning Log Project with no luck.

I'm guessing my queryset is not rendering data correctly? I've read the Django documentation on querysets as well, but nothing I've tried has worked. I'm really new to programming so any help would be appreciated. Thank you!

Here's my models.py:

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

# Create your models here.

class Topic(models.Model):
    """A topic the user is learning about"""
    text = models.CharField(max_length=200)
    date_added = models.DateTimeField(auto_now_add=True)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    public = models.BooleanField(default=False)

    def __str__(self):
        """Return a string representation of the model"""
        return self.text    

class Entry(models.Model):
    """Something specific learned about a topic"""
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
    text = models.TextField()
    date_added = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = 'entries'

    def __str__(self):
        """Retrun a string representation of the model"""
        if len(self.text) < 50:
            return f"{self.text}"
        else:
            return f"{self.text[:50]}..."

views.py:

from django.shortcuts import render, redirect, get_object_or_404 
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import Http404

from .models import Topic, Entry
from .forms import TopicForm, EntryForm




# Create your views here.
def index(request):
    """The home page for Learning Log"""
    return render(request, 'learning_logs/index.html')


#Checks whether logged in user has access to topic
def check_topic_owner(request, topic):
    if topic.owner != request.user:
        raise Http404

def topics(request):
    """Shows all topics"""
    #Gets public topics
    public_topics = Topic.objects.filter(public=True).order_by('date_added')
    #Gets private topics
    if request.user.is_authenticated:
        private_topics = Topic.objects.filter(owner=request.user).order_by('date_added')
        topics = public_topics | private_topics
    else: 
        topics = public_topics

    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)


def topic(request, topic_id):
    """Show a single topic and all its entries"""
    topic = get_object_or_404(Topic, id=topic_id)

    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)

@login_required
def new_topic(request):
    """Add a new topic"""
    if request.method != 'POST':
        #No data submitted; create a blank form.
        form = TopicForm()
    else:
        #POST data submitted; process data
        form = TopicForm(data=request.POST)
        if form.is_valid():
            new_topic = form.save(commit=False)
            new_topic.owner = request.user
            new_topic.save()
            return redirect('learning_logs:topics')

    #Display a blank or invalid form
    context = {'form': form}
    return render(request, 'learning_logs/new_topic.html', context)

@login_required
def new_entry(request, topic_id):
    """Add a new entry for a particular topic"""
    topic = get_object_or_404(Topic, id=topic_id)
    check_topic_owner(request, topic)

    if request.method != 'POST':
        #No data submitted, create a blank form
        form = EntryForm()
    else:
        #POST data submitted; process data
        form = EntryForm(data=request.POST)
        if form.is_valid():
            new_entry = form.save(commit=False)
            new_entry.topic = topic
            if new_entry.topic.owner == request.user:
                new_entry.save()
            else:
                return Http404
            return redirect('learning_logs:topic', topic_id=topic_id)

    #Display a blank or invalid form
    context = {'topic': topic, 'form': form}
    return render(request, 'learning_logs/new_entry.html', context)

@login_required
def edit_entry(request, entry_id):
    """Edit an existing entry"""
    entry = get_object_or_404(Entry, id=entry_id)
    topic = entry.topic
    check_topic_owner(request, topic)

    if request.method != 'POST':
        #Initial request; prefill with the current entry
        form = EntryForm(instance=entry)
    else:
        #POST data submitted; process data
        form = EntryForm(instance=entry, data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('learning_logs:topic', topic_id=topic.id)

    context = {'entry': entry, 'topic': topic, 'form': form}
    return render(request, 'learning_logs/edit_entry.html', context) 

new_topic.html:

{% load bootstrap4 %}

{% block page_header %}
    <h3>Add a new topic:</h3>
{% endblock page_header %}

{% block content %}

    <form method="post" action="{% url 'learning_logs:new_topic' %}" class="form">
        {% csrf_token %}
        {% bootstrap_form form %}
        <div class="form-check">
            <input class="form-check-input" type="checkbox" value=True id="public" />
            <label class="form-check-label" for="public">
                <p>Make post public?<p/>
            </label>

        </div>
        {% buttons %}
            <button name="submit" class="btn btn-primary">Add topic</button>
        {% endbuttons %}
    </form>

{% endblock content %}

And forms.py:

from .models import Topic, Entry

class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = ['text']
        labels = {'text': ''}

class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = ['text']
        labels = {'text': ''}
        widgets = {'text': forms.Textarea(attrs={'cols': 80})}

And topics.html:


{% block page_header %}
    <h1>Topics</h1>
{% endblock page_header %}

{% block content  %}
    <ul>
        {% for topic in topics %}
            <li><h4>
                <a href="{%url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
            </h4></li>

        {% empty %}
            <li><h4>No topics have been added yet.</h4></li>
        {% endfor %}
    </ul>

    <h4><a href="{% url 'learning_logs:new_topic' %}">Add a new topic</a></h4>

    {% endblock content %}

Edit:

I've gotten the project to work as expected now with the help from hasnain095. Here is my updated new_topic.html. I still don't understand how the checkbox is being generated since I've deleted the html which I thought was specifically generating it, but since it's still working, I'm content.

{% extends "learning_logs/base.html" %}
{% load bootstrap4 %}

{% block page_header %}
    <h3>Add a new topic:</h3>

{% endblock page_header %}

{% block content %}

    <form method="post" action="{% url 'learning_logs:new_topic' %}" class="form">
        {% csrf_token %}
        {% bootstrap_form form %}

        {% buttons %}
            <button name="submit" class="btn btn-primary">Add topic</button>
        {% endbuttons %}
    </form>

{% endblock content %}
3 Answers

I think the issue is that you've only specified the field 'text in your Topic Form:

class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = ['text']
        labels = {'text': ''}

but in new_topic.html file, you have only one checkbox input, no place to enter the text

<div class="form-check">
            <input class="form-check-input" type="checkbox" value=True id="public" />
            <label class="form-check-label" for="public">
                <p>Make post public?<p/>
            </label>

        </div>
        {% buttons %}
            <button name="submit" class="btn btn-primary">Add topic</button>
        {% endbuttons %}

Your checkbox for "Make post public?" is getting mapped to the field "text" of your Topic.

To fix do:

class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = ['text', 'public']
        labels = {'text': ''}

and

<div class="form-check">
            <input class="form-check-input" type="checkbox" value=True id="public" />
            <label class="form-check-label" for="public">
                <p>Make post public?<p/>
            </label>

            <input class="form-input" type="input" value=True id="text1" />
            <label class="form-label" for="text1">
                <p>Text<p/>
            </label>


        </div>

Use unions to add to a queryset:

def topics(request):
    """Shows all topics"""
    # Gets public topics (renamed to topic_set to not shadow function name)
    topic_set = Topic.objects.filter(public=True).order_by('date_added')
    # Add private topics, if any. We save some work by not including the public
    # topics.
    if request.user.is_authenticated:
        topic_set = topic_set.union(
            Topic.objects.filter(public=False, owner=request.user)
        )

    context = {'topics': topic_set}
    return render(request, 'learning_logs/topics.html', context)

Side note: add indexes to fields you filter on:

class Topic(models.Model):
    """A topic the user is learning about"""
    text = models.CharField(max_length=200)
    date_added = models.DateTimeField(auto_now_add=True)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    public = models.BooleanField(default=False, db_index=True)

    def __str__(self):
        """Return a string representation of the model"""
        return self.text    

It's working for me after use union to append private topics to public topic, but there maybe some different. list below, hope will help: views.py

def check_topic_owner(topic, request):
if not topic.public:
    if topic.owner != request.user:
        raise Http404   


def topics(request):
#Gets public topics
public_topics = Topic.objects.filter(public=True)
#Gets private topics and append the public topic
if request.user.is_authenticated:
    private_topics = Topic.objects.filter(owner=request.user)
    topics = public_topics.union(private_topics).order_by('date_added')
else: 
    topics = public_topics


context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)

models.py

class Topic(models.Model):

text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
public = models.BooleanField(default=False)

def __str__(self):
    return self.text
Related