I have to do this project of classified ads for Coursera. Essentially every user can post, update and delete advertisements. The problem is that even if the objects are correctly stored (I checked tables and objects via admin and manually with the command line), the ad list doesn't show up and it tells "There are no ads in the database." even though there are ads. I'm looking everywhere but I cannot find the error. I'm using django 3.2.5, here's the code:
- ad_list.html, located in mysite/ads/templates/ads ('base_menu.html' is for bootstrap):
{% extends "base_menu.html" %}
{% block content %}
<h1>Ads</h1>
<p>
{% if ad_list %}
<ul>
{% for ad in ad_list %}
<li>
<a href="{% url 'ads:ad_detail' ad.id %}">{{ ad.title }}</a>
{% if ad.owner == user %}
(<a href="{% url 'ads:ad_update' ad.id %}">Edit</a> |
<a href="{% url 'ads:ad_delete' ad.id %}">Delete</a>)
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no ads in the database.</p>
{% endif %}
</p>
<p>
<a href="{% url 'ads:ad_create' %}">Add an Ad</a> |
{% if user.is_authenticated %}
<a href="{% url 'logout' %}?next={% url 'ads:all' %}">Logout</a>
{% else %}
<a href="{% url 'login' %}?next={% url 'ads:all' %}">Login</a>
{% endif %}
</p>
{% endblock %}
- main.html, where the app should show the list, located in mysite/home/templates/home:
{% extends "base_menu.html" %}
{% block content %}
<head>
<title>Ads</title>
</head>
<body>
<h1>Welcome to {{ settings.APP_NAME }}</h1>
<p>
Hello World.
</p>
</body>
{% endblock content %}
- urls.py:
from django.urls import path, reverse_lazy
from . import views
app_name='ads'
urlpatterns = [
path('', views.AdListView.as_view(), name='all'),
path('ad/<int:pk>', views.AdDetailView.as_view(), name='ad_detail'),
path('ad/create',
views.AdCreateView.as_view(success_url=reverse_lazy('ads:all')), name='ad_create'),
path('ad/<int:pk>/update',
views.AdUpdateView.as_view(success_url=reverse_lazy('ads:all')), name='ad_update'),
path('ad/<int:pk>/delete',
views.AdDeleteView.as_view(success_url=reverse_lazy('ads:all')), name='ad_delete'),
]
- owner.py, created to subclass Generic Views and restrict them to owners:
from django.views.generic import CreateView, UpdateView, DeleteView, DetailView
from django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
class OwnerListView(ListView)
class OwnerDetailView(DetailView)
class OwnerCreateView(LoginRequiredMixin, CreateView)
def form_valid(self, form):
print('form_valid called')
object = form.save(commit=False)
object.owner = self.request.user
object.save()
return super(OwnerCreateView, self).form_valid(form)
class OwnerUpdateView(LoginRequiredMixin, UpdateView)
def get_queryset(self):
print('update get_queryset called')
""" Limit a User to only modifying their own data. """
qs = super(OwnerUpdateView, self).get_queryset()
return qs.filter(owner=self.request.user)
class OwnerDeleteView(LoginRequiredMixin, DeleteView)
def get_queryset(self):
print('delete get_queryset called')
qs = super(OwnerDeleteView, self).get_queryset()
return qs.filter(owner=self.request.user)
- views.py:
from ads.models import Ad
from ads.owner import OwnerListView, OwnerDetailView, OwnerCreateView, OwnerUpdateView, OwnerDeleteView
class AdListView(OwnerListView):
model = Ad
class AdDetailView(OwnerDetailView):
model = Ad
class AdCreateView(OwnerCreateView):
model = Ad
fields = ['title', 'text', 'price']
class AdUpdateView(OwnerUpdateView):
model = Ad
fields = ['title', 'text', 'price']
class AdDeleteView(OwnerDeleteView):
model = Ad
- models.py:
from django.db import models
from django.core.validators import MinLengthValidator
from django.conf import settings
class Ad(models.Model) :
title = models.CharField(
max_length=200,
validators=[MinLengthValidator(2, "Title must be greater than 2 characters")]
)
price = models.DecimalField(max_digits=7, decimal_places=2, null=True)
text = models.TextField()
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
- settings.py:
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
APP_NAME = 'Ads'
SECRET_KEY = 'g$iqqu&*mw4_sg3(#ld0sqaalxebel&168^yj%i&sgrw(fmn@w'
DEBUG = True
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django_extensions',
'crispy_forms',
'rest_framework',
'social_django',
'taggit',
'home.apps.HomeConfig',
'ads.apps.AdsConfig',
]
CRISPY_TEMPLATE_PACK = 'bootstrap3'
TAGGIT_CASE_INSENSITIVE = True
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',
'social_django.middleware.SocialAuthExceptionMiddleware',
]
ROOT_URLCONF = 'mysite.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'home.context_processors.settings',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
],
},
},
]
WSGI_APPLICATION = 'mysite.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'misterObsidian$default',
'USER': 'misterObsidian',
'PASSWORD': 'OBS77743.jG',
'HOST': 'misterObsidian.mysql.pythonanywhere-services.com',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
try:
from . import github_settings
SOCIAL_AUTH_GITHUB_KEY = github_settings.SOCIAL_AUTH_GITHUB_KEY
SOCIAL_AUTH_GITHUB_SECRET = github_settings.SOCIAL_AUTH_GITHUB_SECRET
except:
print('When you want to use social login, please see dj4e-samples/github_settings-dist.py')
AUTHENTICATION_BACKENDS = (
'social_core.backends.github.GithubOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
LOGOUT_REDIRECT_URL = '/'
LOGIN_REDIRECT_URL = '/'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
What I have tried:
- Changed 'ad_list' into 'ads_list';
- Added 'context_object_name' in the template ad_list.html;
- Modified the entire structure, using a simple view-function.
- Deleted the db, and migrate again;
- Get rid of owner.py and directly use a ListView in views.py;
- Copy the template in ad_list.html into main.html. Doing this, the only thing I've gained is the message "There are no ads in the database." which is supposed to be shown when a list doesn't exist;
- Extend ad_list.html into main.html, obtaining the same result as above, plus losing the bootstrap menu