I have a situation where I want to have published functionality for articles, and I have it. The problem is that I can't get my unit test to work properly, trying to post True to the published field.
Here's what my tests look like.
class ArticleDetailViewTest(TestCase):
def setUp(self):
email = 'testuser#gmail.com'
first_name = 'test'
last_name = 'user'
password = 'test15146'
user = User.objects.create_user(email, first_name, last_name, password)
self.client = Client()
self.client.login(username='testuser#gmail.com', password='test15146')
def test_can_publish_article_from_POST(self):
other_article_two = Article.objects.create(name='Test Name One', author=User.objects.get(email='testuser#gmail.com'))
correct_article_two = Article.objects.create(name='Test Name Two', author=User.objects.get(email='testuser#gmail.com'))
response = self.client.post(reverse('publish_article', kwargs={'pk' : correct_article_two.pk}))
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, f'/articles/{correct_article_two.pk}/')
self.assertEqual(correct_article_two.published, True)
Here's what my publish article view looks like.
def publish_article(request, pk):
article = get_object_or_404(models.Article, pk=pk)
if request.method == "POST":
article.published = True
article.save(update_fields=['published'])
return redirect('article_detail_view', pk=article.pk)
else:
raise Http404
Here are my urls.
from django.urls import path
from . import views
urlpatterns = [
path('', views.HomePageView.as_view(), name='home'),
path('new/', views.ArticleCreateView.as_view(), name='article_new'),
path('<int:pk>/', views.ArticleDetailView.as_view(), name='article_detail_view'),
path('<int:pk>/publish/', views.publish_article, name='publish_article'),
path('<int:pk>/unpublish/', views.unpublish_article, name='unpublish_article'),]
Here are my models.
from django.conf import settings
from django.db import models
from django.urls import reverse
from django import forms
class Article(models.Model):
name = models.CharField(max_length=255, blank=False)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
published = models.BooleanField(default=False)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('article_detail_view', args=[str(self.id)])
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['name',]
Lastly, here is the template.
{% extends 'base.html' %}
{% block title %}{{ article.name }}{% endblock %}
{% block content %}
{% if article.author_id == user.pk %}
<div class="row">
<div class="col-6 col-centered">
<h2 class="text-center">{{ article.name }}</h2>
<p class="text-center">by {{ article.author.first_name }} {{ article.author.last_name }}</p>
{% if article.published %}
<small class="form-text text-muted mb-1">This article is public.</small>
<form method="post" action="/articles/{{ article.pk }}/unpublish/">
{% csrf_token %}
<input type="submit" name="unpublish" value="Unpublish Article" id="id_unpublish_button" class="btn btn-primary btn-md"/>
</form>
{% else %}
<small class="form-text text-muted mb-1">This article is private.</small>
<form method="post" action="/articles/{{ article.pk }}/publish/">
{% csrf_token %}
<input type="submit" name="publish" value="Publish Article" id="id_publish_button" class="btn btn-primary btn-md"/>
</form>
{% endif %}
</div>
</div>
{% elif article.published %}
<div class="row">
<div class="col-6 col-centered">
<h2 class="text-center">{{ article.name }}</h2>
<p class="text-center">by {{ article.author.first_name }} {{ article.author.last_name }}</p>
</div>
</div>
{% else %}
<div class="row">
<div class="col-6 col-centered">
<p class="text-center">This article is private.</p>
</div>
</div>
{% endif %}
{% endblock %}
This is the error message I'm getting from my test. The issue seems to be I can post to the URL with self.client.post . Any help would be greatly appreciated.
FAIL: test_can_publish_article_from_POST (articles.tests.ArticleDetailViewTest)
Traceback (most recent call last):
File "/Users/christopher.chough/article_directory/articles/tests.py", line 126, in test_can_publish_article_from_POST
self.assertEqual(correct_article_two.published, True)
AssertionError: False != True
Ran 17 tests in 2.340s
FAILED (failures=1)
Object in your test method not updated. You can use refresh_from_db method to update it after changes:
def test_can_publish_article_from_POST(self):
other_article_two = Article.objects.create(name='Test Name One', author=User.objects.get(email='testuser#gmail.com'))
correct_article_two = Article.objects.create(name='Test Name Two', author=User.objects.get(email='testuser#gmail.com'))
response = self.client.post(reverse('publish_article', kwargs={'pk' : correct_article_two.pk}))
correct_article_two.refresh_from_db() # Add this line
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, f'/articles/{correct_article_two.pk}/')
self.assertEqual(correct_article_two.published, True)
Related
I am trying to make it so when I view a blog from any particular link on my website, the url displays www.example.com/view-blog/slug. I have got it working for my images and image title. But I cannot get it to work with blog titles.
It keeps returning the error:
Reverse for 'viewblog' with arguments '('',)' not found. 1 pattern(s) tried: ['view\-blog/(?P[-a-zA-Z0-9_]+)/\Z'].
Models.py
class BlogPost(models.Model):
blog_title = models.CharField(max_length=100, null=False, blank=False, default="")
slug = models.SlugField()
blog_article = RichTextUploadingField(null=True, blank=True, default="ici")
blog_image = models.ImageField(null=True, blank=True, upload_to="images", default="default.png")
blog_date = models.DateField(auto_now_add=True)
blog_published = models.BooleanField(default=False)
blog_featured = models.BooleanField(default=False)
def save(self, *args, **kwargs):
self.slug = self.slug or slugify(self.blog_title)
super().save(*args, **kwargs)
def __str__(self):
return self.blog_title
Views.py EDIT: ADDED ALL VIEWS.
def blogPage(request):
posts = BlogPost.objects.filter(blog_date__lte=timezone.now()).order_by('-blog_date')[:4]
context = {'posts': posts}
return render(request, 'blog.html', context)
def blogsPage(request):
posts = BlogPost.objects.filter(blog_date__lte=timezone.now()).order_by('-blog_date')
context = {'posts': posts}
return render(request, 'blogs.html', context)
def searchBlogs(request):
if request.method == "POST":
searched = request.POST.get('searched', False);
searched_blogs = BlogPost.objects.filter(blog_title__icontains=searched)
return render(request, 'search-blogs.html', {'searched': searched, 'searched_blogs': searched_blogs})
def viewBlog(request, slug):
try:
blog = BlogPost.objects.get(slug=slug)
except BlogPost.DoesNotExist:
print("ViewBlog with this slug does not exist")
blog = None
return render(request, 'view-blog.html', {'blog': blog, 'slug': slug})
Urls.py
urlpatterns = [
path('', views.galleryPage, name='gallery'),
path('gallery/', views.galleryPage, name='gallery'),
path('contact/', views.contactPage, name='contact'),
path('blog/', views.blogPage, name='blog'),
path('blogs/', views.blogsPage, name='blogs'),
path('search-blogs/', views.searchBlogs, name='searchblogs'),
path('view-image/<slug:slug>/', views.viewImage, name='viewimage'),
path('view-blog/<slug:slug>/', views.viewBlog, name='viewblog'),
path('thank-you/', views.thankyouPage, name='thankyou'),
path('about-me/', views.aboutmePage, name='aboutme'),
]
**One of my templates I wish to link to viewblog page from ** ( I have tried to replace .id on my urls with .slug but this is what gives me the error.
<body>
<header>{% include 'navbardesktop.html' %}</header>
<div class="blog-container">
<h1 class="all-blog-title" id="all-blog-title" style="text-align: center;">All Blogs</h1>
<br>
<div class="search">
<form class="d-flex" method="POST" action="{% url 'searchblogs' %}">
{% csrf_token %}
<i class="fa fa-search"></i>
<input type="text" class="form-control" placeholder="Search keyword" name="searched">
<button id="search-btn" class="btn btn-secondary">Search</button>
</form>
</div>
<br>
<hr id="blog-hr" style="width:50%; margin-left:25% !important; margin-right:25% !important;" />
<div class="blog-post-container">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Blog List
</button>
<ul class="dropdown-menu" style="background-color: #d6d6d6 ;">
{% for post in posts %} {% if post.blog_published is True %}
<li><a class="dropdown-item" href="{% url 'viewblog' post.id %}">{{post.blog_title}}</a></li>
{% endif %}
{% endfor %}
</ul>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
</div>
<div class="row">
{% for post in posts %} {% if post.blog_published is True %}
<div class="col-md-4" id="blog-col">
<div class="blog-post">
<div class="blog-content">
<img class="blog-img"src="{{post.blog_image.url}}"alt="My image"/>
<h2 class="blog-title">{{post.blog_title}}</h2>
<hr id="blog-hr" style="width: 90%" />
<article class="blog-article">
<p>{{post.blog_article|truncatechars_html:265|safe}}</p>
</article>
Read More...
<p class="blog-date">Posted on: {{post.blog_date}}</p>
</div>
</div>
</div>
{% endif %} {% empty %}
<h3>No Blog Uploads</h3>
{% endfor %}
</div>
</div>
</div>
</div>
the error message
Reverse for 'viewblog' with arguments '('',)' not found.
Remark: what is '('',)' ??
it is simply the print of a python list () with one empty argument ('',). The comma just indicates it is a list. This is placed in '' to structure the error message text '('',)'
so this is the list of arguments that is extracted from your {% url .... %} statement for the error message
always means you have and url tag pointing to "viewblog" that requires an argument (because definded like that in urlpatterns in urls.py ) but actually one of them is null or "" (which is not accepted to reverse a valid url):
urlpatterns = [
...
path('view-blog/<slug:slug>/', views.viewBlog, name='viewblog'),
...
]
---------------------------------
href="{% url 'viewblog' post.id %}"
Now in your case you had originally post.id as parameter (I understood you changed that to post.slug). That means one of the post database entries has a post.slug that is null or ""
to debug just print the value of post.slug into the html page and you will see what is wrong. the "xxx" and "yyy" are there to mark on the output clearly where some content shoult appear (because without it is more difficult to see if there is an empty output).
{% for post in posts %}
xxx {{ post.slug }} xxx <br>
yyy {{ post.blog_title }} xxx <br>
{% endfor %}
Another way of checking post.slug is to go to the admin page ouf your app and inspect the database entries directly
And a third one would be to print the query result for posts in the view:
def blogsPage(request):
posts = BlogPost.objects.filter(blog_date__lte=timezone.now()).order_by('-blog_date')
context = {'posts': posts}
for post in posts:
print(post)
return render(request, 'blogs.html', context)
which will show the content of posts in the console
How are you?
I m totally new in Django.I designed a page and I wanted to show a django form(edit or create) in a well designed HTML page. but i do not know how.
This is my owner method:
class OwnerUpdateView(LoginRequiredMixin, UpdateView):
"""
queryset to the requesting user.
"""
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(user=self.request.user)
class OwnerCreateView(LoginRequiredMixin, CreateView):
"""
Sub-class of the CreateView to automatically pass the Request to the Form
and add the owner to the saved object.
"""
# Saves the form instance, sets the current object for the view, and redirects to get_success_url().
def form_valid(self, form):
print('form_valid called')
object = form.save(commit=False)
object.user = self.request.user
object.save()
return super(OwnerCreateView, self).form_valid(form)
This is my views.py
class TaskUpdateView(OwnerUpdateView):
model = Task
fields = ["title", "text", "endDate"]
class TaskCreateView(OwnerCreateView):
model = Task
fields = ["title","text","status","endDate"]
This is my urls.py:
app_name='task'
urlpatterns = [
path('', views.TaskListView.as_view(), name='all'),
path('task/<int:pk>/', views.TaskDetailView.as_view(), name='detail'),
path('task/create', views.TaskCreateView.as_view(success_url=reverse_lazy('task:all')), name='task_create'),
path('task/update/<int:pk>', views.TaskUpdateView.as_view(success_url=reverse_lazy('task:all')),
name='task_update'),
path('task/delete/<int:pk>', views.TaskDeleteView.as_view(success_url=reverse_lazy('task:all')),
name='task_delete'),
path("accounts/login/", views.login, name='login'),
path("accounts/logout/", views.logout, name='logout'),
]
And this is the models.py:
class Task(models.Model):
title=models.CharField(max_length=250)
text=models.TextField()
user=models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False)
status=models.ForeignKey('Status',on_delete=models.SET_NULL,null=True)
startDate=models.DateTimeField(auto_now_add=True)
endDate=models.DateField(null=True)
def __str__(self):
return self.title
class Status(models.Model):
name=models.CharField(max_length=250)
def __str__(self):
return self.name
And this is where these both function work:
{%extends 'base.html'%}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>{{ form.as_table }}</table>
<input type="submit" value="Submit">
{# <input type="submit" onclick="window.location='{% url 'project:all' %}' ; return false;" value="Cancel">#}
</form>
{% endblock %}
How can i separate each element of this form and put it in a better designed page?
Thanks
There are two ways:
Option 1:
Loop over the form fields and render them individually:
{% for field in form %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<span class="form-text">{{ field.help_text|safe }}</span>
{% endif %}
</div>
{% endfor %}
See docs for more.
Option 2:
You can manually create form inputs and give them the correct field name attribute. This gives you more control but also requires more work:
<div class="form-group"
<input
type="text"
name="title"
value="{{ form.title.value }}"
class="form-control {% if form.title.errors %}is-invalid{% endif %}"
>
{% if form.title.help_text%}
<span class="form-text">{{ form.title.help_text|safe }}</span>
{% endif %}
<div class="invalid-feedback">{{ form.title.errors }}</div>
</div>
<!-- now do the same for other fields -->
I am struggling passing data to a form using pytest. I wish to pass the username and password to a login form. Here is what I have tried:
conftest.py
import pytest
from wahoo_connect import init_app, db
from wahoo_connect.models import User
from dotenv import load_dotenv
load_dotenv('.flaskenv')
#pytest.fixture(scope='module')
def app():
app = init_app()
with app.app_context():
db.create_all()
user = User(username='testuser', email='test#gmail.com', forename='Test', surname='User', confirmed=True)
user.set_password('testing')
db.session.add(user)
db.session.commit()
yield app
#pytest.fixture(scope='module')
def client(app):
return app.test_client()
this sets up a client fixture which is used by my test:
def test_index_page__logged_in(client):
with client:
formdata = {'username': 'testuser', 'password': 'testing', 'remember_me': False}
client.post('/auth/login', data=formdata)
assert current_user.username == 'testuser'
the route is defined in a blueprint as:
#auth_bp.route('/login', methods=['GET', 'POST'])
def login():
# Login route logic goes here
if current_user.is_authenticated:
return redirect(url_for('home_bp.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password', 'warning')
return redirect(url_for('auth_bp.login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('home_bp.index')
return redirect(next_page)
return render_template('auth/login.html', title='Sign In', form=form)
and here is the form:
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
and the template:
{% extends "base.html" %}
{% block content %}
<div class="form-signin text-center">
<form action="" method="post" novalidate>
<h3>Please sign in</h3>
<div class="form-floating">
{{ form.username(type="text", class="form-control", id="floatingInput", placeholder="Username") }}
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
<label for="floatingInput">{{ form.username.label.text }}</label>
</div>
<div class="form-floating">
{{ form.password(type="password", class="form-control", id="floatingPassword", placeholder="Password") }}
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
<label for="floatingPassword">{{ form.password.label.text }}</label>
</div>
<div class="checkbox mb-3">
<label>
{{ form.remember_me() }} {{ form.remember_me.label.text }}
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ form.submit.label.text }}</button>
{{ form.hidden_tag() }}
</form>
</div>
<p class="text-center">Forgot Your Password? Click to Reset</p>
<p class="text-center pt-3">Create an Account</p>
{% endblock %}
I can see the data before the "if form.validate_on_submit():" statement in the view and it is correct, but I can never get beyond this point in the submission process. I seem to be missing something!
Thank you
Martyn
It seems that I was missing an important flag:
WTF_CSRF_ENABLED = False
it now works!
I guess that I will ask a very simple question, but it is a sign that I still do not get something.
I have a team model and I would like to display a list of all the team that the logged in user created.
I tried with
{% extends 'base.html' %}
{% block body %}
<div class="container">
<div class="jumbotron">
<h2>Select one of your team and link it to your project</h2>
</div>
<div class="col-md-8 col-md-offset-2">
{% for i in team_set.all %}
<p>{{ i.team_name }}</p>
{% endfor %}
</div>
</div>
{% endblock %}
But first it does not display anything and it is suppose to show all the team and not only the teams that the current logged in user created.
COuld you please give me a hand ?
model.py :
class Team(models.Model):
team_name = models.CharField(max_length=100, default = '')
team_hr_admin = models.ForeignKey(MyUser, blank=True, null=True)
members = models.ManyToManyField(MyUser, related_name="members")
def __str__(self):
return self.team_name
view.py:
class LinkTeam(TemplateView):
template_name= 'link_project.html'
url.py:
from django.conf.urls import url, include
from website import views
app_name = 'website'
urlpatterns = [
url(r'^hr_index/$', views.HRIndex.as_view(), name='hr_index'),
url(r'^addproject/$', views.ProjectCreate.as_view(), name='add_project'),
url(r'^addteam/$', views.TeamCreate.as_view(), name='add_team'),
url(r'^linkteam/$', views.LinkTeam.as_view(), name='Link_team'),
url(r'^project/(?P<pk>[0-9]+)/$',views.ProjectDetailView.as_view(), name='ProjectDetails'),
]
Simply, you can try this
in the View
class LinkTeam(TemplateView):
template_name= 'link_project.html'
def get(request):
courts = Yourmodel.objects.all() # worth looking into?
return render_to_response(self.template_name, {'courts': courts})
AND in HTML:
<div class="col-md-8 col-md-offset-2">
{% for i in courts %}
<p>{{ i.team_name }}</p>
{% endfor %}
</div>
This view is supposed to find a blog post and change it's information, but instead of that it just makes a new Blog object with the new (and old) information.
The update view
#login_required
def view_updatepost(request, blog_id):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
#post = Blog.objects.get(pk=blog_id)
post_to_be_changed = get_object_or_404(Blog, pk=blog_id)
form = BlogForm(request.POST or None, instance=post_to_be_changed)
if form.is_valid():
post_to_be_changed = form.save(commit=False)
#
#
post_to_be_changed.save()
#messages.success(request, "<a href='#'>Item</a> Saved", extra_tags='html_safe')
return HttpResponseRedirect(post_to_be_changed.get_absolute_url())
context = {
'post_to_be_changed': post_to_be_changed,
'form': form,
}
return render(request, 'blog/makepost.html', context)
The template used by the view makepost.html
{% extends "base.html" %}
{% load staticfiles %}
{% block main_content %}
<!-- Page Header -->
<!-- Set your background image for this header on the line below. -->
<header class="intro-header" style="background-image: url('{% static "img/about-bg.jpg" %}')">
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="page-heading">
<h1>Make a Post</h1>
<hr class="small">
<span class="subheading">Share with the World.</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
{% if not user.is_authenticated %}
You must be <u>logged in</u> to make a post.
{% else %}
<form action="{% url "makepost" %}" method="post">
{% csrf_token %}
{{form.as_p}}
<div align="center">
<input class="btn btn-default" type="submit" value="Post to Blog" onclick="window.location='{% url "" %}';"/>
{# Home #}
</div>
</form>
{% endif %}
</div>
</div>
</div>
<hr>
{% endblock main_content %}
The models.py
from django.db import models
import datetime
# Create your models here.
class Blog(models.Model):
title = models.CharField(max_length=250)
subtitle = models.CharField(max_length=250, null = True, blank=True)
date_added = models.DateTimeField(default=datetime.datetime.now())
image = models.TextField(max_length=1000, null = True, blank=True)
tags = models.TextField(max_length=500, null=True, blank=True)
article = models.TextField(max_length=15000, null=True, blank=True)
author = models.CharField(max_length=150, null=True, blank=True)
def get_absolute_url(self):
return "/blog/%i" % self.pk
The forms.py
from django import forms
from .models import Blog
import datetime
class PostForm(forms.Form):
title = forms.CharField()
subtitle = forms.CharField(required=False)
date_added = forms.DateTimeField()
image = forms.URLField(required=False)
tags = forms.CharField(required=False)
article = forms.CharField()
author = forms.CharField()
class BlogForm(forms.ModelForm):
class Meta:
model = Blog
fields = ('title', 'subtitle',
'image', 'tags', 'article')
It seems that you are not referring to your update view in your form action url:
<form action="{% url **"makepost"** %}" method="post">