I have Article and ArticleCategory in my model. Article has many categories.
MODELS
class ArticleCategory(Created):
category_name = models.CharField(max_length=128)
slug = models.SlugField(null=False, unique=False)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.category_name)
return super().save(*args, **kwargs)
def __str__(self):
return self.category_name
class Article(Created):
title = models.CharField(max_length=120)
author = models.ForeignKey(User, on_delete=models.CASCADE)
snippet = models.TextField(null=False) # ustawić max_lenght
body = RichTextField(null=False)
category = models.ManyToManyField(ArticleCategory, related_name='articles') # TODO: ustawić on_delete
image = models.ImageField(blank=True, null=True, upload_to='article_image/')
slug = models.SlugField(null=False, unique=False)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
return super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('news:article_detail', kwargs={'pk': self.pk, 'slug': self.slug})
Also I have Url to specific category with all Articles with this category:
URL
urlpatterns = [
path('show/<int:pk>/<slug:slug>', ArticleDetailView.as_view(), name='article_detail'),
path('all/', AllArticlesListView.as_view(), name='all_articles_list'),
path('category/<slug:slug>/', CategoryArticlesList.as_view(), name='category_articles_list'),
]
In the VIEW i created
class CategoryArticlesList(DetailView):
template_name = 'news/category_articles_list.html'
model = ArticleCategory
And finally template CATEGORY_ARTICLES_LIST.HTML
SPECIFIC CATEGORY: {{ articlecategory.category_name | upper }}
AND ALL NEWS WITH THIS CATEGORY
{% for article in articlecategory.articles.iterator reversed %}
<h3>{{ article.title }}</h3> <br>
{{ article.body | safe }} <br>
{% endfor %}
My question is... How can I make Pagination to all specific category articles in Views?
If I use ListView in class CategoryArticlesList there will be big problem with query form me... I dont know what I should do.
A DetailView does not do pagination, since, well there is only a single object. You can howver make it a ListView and add some logic to pass the articlecategory:
from django.shortcuts import get_object_or_404
class ArticlesByCategoryListView(ListView):
template_name = 'news/category_articles_list.html'
model = Article
queryset = Article.objects.order_by('-pk')
paginate_by = 25
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
category__slug=self.kwargs['slug']
)
def articlecategory(self):
return get_object_or_404(ArticleCategory, slug=self.kwargs['slug'])
then in the template, we can render this with:
SPECIFIC CATEGORY: {{ view.articlecategory.category_name|upper }}
AND ALL NEWS WITH THIS CATEGORY
{% for article in page_obj %}
<h3>{{ article.title }}</h3> <br>
{{ article.body|safe }} <br>
{% endfor %}
Related
I have a generic DetailView and I'm trying to do a form for the comment of an user after I display the details of the model but I keep getting the error 'ProductFeedbackView' object has no attribute 'get_form'.
I don't know if the templates have any problem because the error is in the view when I try to get the form into a variable.
Here is comment's model:
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
...
def get_absolute_url(self):
return reverse('product-feedback', kwargs={'pk': self.pk})
Comment's form:
class CommentForm(forms.ModelForm):
content = forms.CharField()
class Meta:
model = Comment
fields = ['content']
View:
class ProductFeedbackView(DetailView):
model = Product
template_name = 'services/product-feedback.html'
form_class = CommentForm
def get_success_url(self):
return reverse('product-feedback', kwargs={'pk': self.object.id})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = CommentForm(initial={'content': self.object})
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.instance.author = self.request.user
form.save()
return super().form_valid(form)
urls's:
...
path('feedback/<int:pk>/', ProductFeedbackView.as_view(), name='product-feedback'),
Template:
Details
Feedback
<p>{{ product.author }}</p>
<h1>{{ product.title }}</h1>
<p>{{ product.description }}</p>
{% if user.is_authenticated %}
<form method="POST">
<label for="comment">Type comment</label>
{{ form.as_p }} {% csrf_token %} <input type="submit" value="Post">
</form>
{% else %}
...
{% endif %}
{% for comment in comment.service.all %}
<p>{{ comment.author }}</p>
<p>{{ comment.content }}</p>
{% endfor %}
Product model:
class Product(models.Model):
author = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
title = models.CharField(max_length=120, unique=True)
category = models.ForeignKey(Category, default=None, on_delete=models.PROTECT)
description = models.CharField(max_length=300, blank=True, null=True)
...
views = models.IntegerField(default=0)
featured = models.BooleanField(default=False)
date_posted = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('product-detail', kwargs={'pk': self.pk})
You need to inherit the form view mixing like so:
from django.views.generic.edit import FormMixin
class ProductFeedbackView(DetailView, FormMixin):
...
As per django classy class based views guide:
https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/FormView/
However, mixing a detail view and an edit view probably doesn't match the usual djagno-esque methodology.
Youre likely better off having a detail view and an edit view (FormView) respectively and using the built in behaviour of django.
Just add an edit button to your detail page which is a reverse to the form view :)
If you need more help, just ping a comment.
I'm building an app where I can add recipes and add ingredients to those recipes. On view recipe_details I have a button to add_new_ingredient. When I'm on new_ingredient_form I want to have back button to get back to the details of recipe. I'm trying to pass recipe's pk but it doesn't work. How am I able to pass recipe's pk to be able to back to previous view?
models.py
class Recipe(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
title = models.CharField(max_length=100, unique=True)
preparation = models.TextField()
def __str__(self):
return self.title
class Ingredient(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
amount = models.PositiveSmallIntegerField(blank=True, null=True)
unit = models.ForeignKey('Unit', on_delete=models.SET_NULL, blank=True, null=True)
def __str__(self):
return self.name
views.py
class RecipeView(generic.DetailView):
model = Recipe
context_object_name = 'recipe'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ingredients_list'] = Ingredient.objects.filter(recipe=self.object.pk)
return context
class AddIngredientView(generic.edit.CreateView):
model = Ingredient
fields = [
'name',
'amount',
'unit'
]
success_url = '/'
template_name = 'recipes/add_ingredient.html'
def dispatch(self, request, *args, **kwargs):
self.recipe = get_object_or_404(Recipe, pk=self.kwargs['pk'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.recipe = self.recipe
return super().form_valid(form)
def get_success_url(self):
if 'add_another' in self.request.POST:
url = reverse_lazy('recipes:add_ingredient', kwargs={'pk': self.object.recipe_id})
else:
url = reverse_lazy('recipes:recipe', kwargs={'pk': self.object.recipe_id})
return url
add_ingredient.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Save</button>
<button class="btn btn-success" type="submit" name="add_another">Save and add another</button>
Back
</form>
{% endblock %}
You can go to the previous page by adding {{request.META.HTTP_REFERER}} to the href property of the button.
Django request to find previous referrer
I have 2 models, City and Country. I want to show cities in the country on a page based on the country selected. I tried to query the Country model passing it to the City model to find all cities related to the country but currently, pages show all cities no matter what is the country.
How I can show cities in one country when the user selects the country from the page?
Any help or suggestions are highly appreciated.
models.py
class City(models.Model):
country = models.ForeignKey('Country', on_delete=models.CASCADE, related_name='country')
name = models.CharField(max_length=90, verbose_name='City name')
slug = models.SlugField(null=True, unique=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('city:cities', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
return super().save(*args, **kwargs)
class Country(models.Model):
country_name = models.CharField(max_length=50, verbose_name='Country name',unique=True)
slug = models.SlugField(null=True, unique=True)
def __str__(self):
return self.country_name
def get_absolute_url(self):
return reverse('city:cities', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.country_name)
return super().save(*args, **kwargs)
views.py
class CountriesListView(ListView):
template_name = "countries.html"
model = Country
context_object_name = "countries"
class CitiesListView(ListView):
template_name = "cities_list.html"
model = City
context_object_name = "cities"
def get_context_data(self, *args, **kwargs):
context = super(CitiesListView, self).get_context_data(*args,**kwargs)
countries = Country.objects.all()
cities = City.objects.filter(country__in=countries)
context['cities'] = cities
return context
templates
# countries.html
<h1>Countries</h1>
{% for country in countries %}
<h3><a href="{% url 'city:cities' country.slug %}">{{ country.country_name }}</h3>
{% endfor %}
# cities_list.html
<h1>Cities</h1>
{% for city in cities %}
<div>
<h3>{{ city }}</h3>
</div>
{% endfor %}
The related_name=… parameter [Django-doc] is the name of the relation in reverse, so to access the City objects of a specific Country. Therefore naming it Country is not a good idea. You can rename it to cities for example:
class City(models.Model):
country = models.ForeignKey(
'Country',
on_delete=models.CASCADE,
related_name='cities'
)
# …
You can use a DetailView to render a single Country, so:
from django.views.generic.detail import DetailView
class CountryDetailView(DetailView):
template_name = 'country_detail.html'
model = Country
context_object_name = 'country'
In the urls, you can thus register this view under the name cities with a slug field:
# app/urls.py
from django.urls import path
from . import views
app_name='city'
urlpatterns = [
# …,
path(
'country/<slug:slug>',
views.CountryDetailView.as_view(),
name='cities'
),
]
In the template, you can then access the relation in reverse:
<!-- country_detail.html -->
{{ country }}
{% for city in country.cities.all %}
{{ city }}
{% endfor %}
so I am trying to add a category system for posts by following a tutorial on this website https://djangopy.org/how-to/how-to-implement-categories-in-django/ (I changed my code up a little)
Everything works like creating categories, adding a post, viewing a post, but if I try to go to the category page to view posts only in that category so /category/CATNAME but it shows me this error
'Category' object has no attribute 'post_set'
models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils.text import slugify
from markdownx.models import MarkdownxField
from markdownx.utils import markdownify
from taggit.managers import TaggableManager
class Category(models.Model):
name = models.CharField(max_length=100)
short_desc = models.CharField(max_length=160)
slug = models.SlugField()
parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.CASCADE)
class Meta:
unique_together = ('slug', 'parent',)
verbose_name_plural = "Categories"
def __str__(self):
full_path = [self.name]
k = self.parent
while k is not None:
full_path.append(k.name)
k = k.parent
return ' -> '.join(full_path[::-1])
def save(self, *args, **kwargs):
value = self.title
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
class Thread(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
content = MarkdownxField()
tags = TaggableManager()
slug = models.SlugField(unique=True)
category = models.ForeignKey('Category', null=True, blank=True, 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
class Meta:
verbose_name_plural = 'Threads'
def get_cat_list(self):
k = self.category
breadcrumb = ["dummy"]
while k is not None:
breadcrumb.append(k.slug)
k = k.parent
for i in range(len(breadcrumb)-1):
breadcrumb[i] = '/'.join(breadcrumb[-1:i-1:-1])
return breadcrumb[-1:0:-1]
def save(self, *args, **kwargs):
value = self.title
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
views.py
from django.shortcuts import render, redirect, get_object_or_404
from .models import Category, Thread
from .forms import NewThreadForm
def show_thread_view(request, hierarchy=None):
category_slug = hierarchy.split('/')
category_queryset = list(Category.objects.all())
all_slugs = [ x.slug for x in category_queryset ]
for slug in category_slug:
if slug in all_slugs:
# parent = get_object_or_404(Category, slug=slug, parent=parent)
parent = Category.objects.filter(slug__in=category_slug, parent=None).first()
thread = get_object_or_404(Thread, slug=slug)
instance = get_object_or_404(Thread, slug=slug)
breadcrumbs_link = instance.get_cat_list()
category_name = [' '.join(i.split('/')[-1].split('-')) for i in breadcrumbs_link]
breadcrumbs = zip(breadcrumbs_link, category_name)
context = {
'thread': thread,
'instance': instance,
'breadcrumbs': breadcrumbs,
}
return render(request, "forums/threads/thread_detail.html", context)
def show_category_view(request, hierarchy=None):
category_slug = hierarchy.split('/')
category_queryset = list(Category.objects.all())
all_slugs = [ x.slug for x in category_queryset ]
parent = None
for slug in category_slug:
if slug in all_slugs:
#parent = get_object_or_404(Category, slug=slug, parent=parent)
parent = Category.objects.filter(slug__in=category_slug, parent=None).first()
context = {
'category': parent,
'post_set': parent.post_set.all(),
'sub_categories': parent.children.all(),
}
return render(request, "forums/categories.html", context)
def new_thread_form_view(request):
if request.method == "POST":
form_data = request.POST or None
form = NewThreadForm(form_data)
if form.is_valid():
news = form.save(commit=False)
news.author = request.user
news.save()
return redirect('/forums')
else:
form = NewThreadForm()
context = {
'form': form
}
return render(request, "forums/threads/thread_form.html", context)
categories.html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<br>
{% if sub_categories %}
<h3>Sub Categories</h3>
{% for i in sub_categories %}
{{ i.name }}
{% endfor %}
{% endif %}
<div class="row small-up-1 medium-up-3" >
{% if post_set %}
{% for i in post_set %}
<div class="columns">
<div class=" card-article-hover card">
<a href="{{ i.slug }}">
<img src="{{ i.cover_photo.url }}">
</a>
<div class="card-section">
<a href="{{ i.slug }}">
<h6 class="article-title">{{ i.title | truncatechars:30}}</h6>
</a>
</div>
<div class="card-divider flex-container align-middle">
{{ i.user.get_full_name }}
</div>
<div class="hover-border">
</div>
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}
The model which has ForeignKey relation to Category model is Thread(as per shared code from the question). So you need to use parent.thread_set.all() to get all the threads related to that category. Also if you define related_name inside Thread to Category ForeignKey like following example:
class Thread(..):
category = models.ForeignKey('Category', null=True, blank=True, on_delete=models.CASCADE, related_name='threads')
Then you can get the threads by parent.threads.all(). More information can be found in documentation.
I want add comment to post on his page ("post detail" page).
I was find answer, but it create comment on other page. I want create comment on page of "post detail".
urls.py
url(r'^post/(?P<pk>\d+)/create/$', views.CommentCreate.as_view(), name='comment_create'),
models.py
class Comment(models.Model):
description = RichTextUploadingField()
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
comment_date = models.DateTimeField(auto_now_add=True, null=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
class Meta:
ordering = ["-comment_date"]
def __str__(self):
return "{}".format(self.description)
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['description']
views.py
class PostDetailView(generic.DetailView):
model = Post
class CommentCreate(LoginRequiredMixin, CreateView):
model = Comment
fields = ['description']
def get_context_data(self, **kwargs):
context = super(CommentCreate, self).get_context_data(**kwargs)
context['post'] = get_object_or_404(Post, pk = self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post=get_object_or_404(Post, pk = self.kwargs['pk'])
return super(CommentCreate, self).form_valid(form)
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.kwargs['pk'],})
comment_form.html
...
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<button type="submit">Submit</button>
</form>
...
post_detail.html
...
{% for comment in post.comment_set.all %}
<p>{{ comment.author }} ({{ comment.comment_date }}) {{ comment.description|safe }}</p>
{% endfor %}
<hr>
{% if user.is_authenticated %}
<p>Add a new comment</p>
...
I think, "comment_form" need to redirect to "post_detail", not generate new page for comment form.
And сould you tell, which parameters has a RichTextField (CKE), how change width, height field only in comment?
If you want the comment form right in your detail page then all you have to do is to add the form and post function in your View,
class PostDetailView(DetailView):
model = Post
template_name = 'yourdetailpage.html'
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['commentform'] = CommentForm()
return context
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk)
form = CommentForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.post = post
obj.author = self.request.user
obj.save()
return redirect('detail', post.pk)
And now you can add your form in your html. And add a submit button to post comment.