UPDATE
I was able to accomplish this in my Project models.py, but I feel this belongs in my view. How would I go about doing this? Code:
#property
def related_project_set(self):
category_id_list = self.category.values_list("id", flat=True)
return Project.live.filter(category__id__in=category_id_list).exclude(id=self.id)[:5]
I'd like to display "projects" related by "category" in my generic class-based detail view for project. I'm also using the Django-Braces PrefetchRelated Mixin. But I don't understand why the Python interpreter displays different results than my template.
For example, given two projects in the db, I get the following result:
>>> from projects.models import Project
>>> Project.objects.all().prefetch_related('category')
[<Project: Avalon Road>, <Project: Piedmont Avenue>]
Models:
class Project(models.Model):
"""
A project completed by ICON.
"""
LIVE_STATUS = 1
DRAFT_STATUS = 2
STATUS_CHOICES = (
(LIVE_STATUS, 'Live'),
(DRAFT_STATUS, 'Draft'),
)
title = models.CharField(max_length=200)
slug = models.SlugField(help_text='Populates from title field.')
date_completed = models.DateField(null=True, blank=True)
vison_description = models.TextField(help_text='Please use Markdown syntax. No HTML is allowed.')
work_description = models.TextField(help_text='Please use Markdown syntax. No HTML is allowed.')
category = models.ManyToManyField(Category)
lead_photo = models.ImageField(upload_to='projects/photos', help_text='Will also be used as thumbnail for category listing.')
gallery = models.ForeignKey(Gallery)
status = models.IntegerField(choices=STATUS_CHOICES, default=2, help_text="Only projects with a status of 'live' will be displayed publicly.")
objects = models.Manager()
live = LiveProjectManager()
class Category(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(help_text='Populates from title field.')
description = models.TextField(help_text='Please use Markdown syntax. No HTML is allowed.', blank=True)
class Meta:
verbose_name_plural = 'Categories'
ordering = ('title',)
def __str__(self):
return self.slug
#models.permalink
def get_absolute_url(self):
return ('categories:detail',
(),
{'slug': self.slug})
Views:
from __future__ import absolute_import
from braces.views import PrefetchRelatedMixin
from django.shortcuts import render
from django.views.generic import DetailView, ListView
from .models import Project
from categories.models import Category
from testimonials.models import Testimonial
class ProjectDetailView(PrefetchRelatedMixin, DetailView):
prefetch_related = ['category']
queryset = Project.live.all()
def get_context_data(self, **kwargs):
"""
Allow the project detail view to display a list of testimonials.
"""
context = super(ProjectDetailView, self).get_context_data(**kwargs)
context['testimonial_list'] = Testimonial.displayed.all()
return context
class ProjectListView(ListView):
queryset = Project.live.all()
def get_context_data(self, **kwargs):
"""
Allow the project list view to display a list of categories.
"""
context = super(ProjectListView, self).get_context_data(**kwargs)
context['category_list'] = Category.objects.all()
return context
Template snippet:
<ul>
{% for project in object.category.all %}
<li>{{ project.title }}</li>
{% endfor %}
</ul>
Related
I am trying to make a drop down select form (category) in django. The form renders well on the webpage but when I try to submit I get the error Select a valid choice. That choice is not one of the available choices.
I have done everything possible within my capability to resolve this. if you have any idea on how to go about this please help.
model.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
# Create your models here.
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('detail', args=[str(self.id)])
# return reverse('home')
class Post(models.Model):
title = models.CharField(max_length=100)
text = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
edited = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
category = models.CharField(max_length=100, default='coding')
def __str__(self):
return f'{self.title} by {self.author} {self.pk}'
def get_absolute_url(self):
return reverse('detail', args=[str(self.id)])
# return reverse('home')
form.py
from django import forms
from .models import Post, Category
choices = Category.objects.all().values_list('name','name')
choices_list = []
for item in choices:
choices_list.append(item)
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'category', 'author','text')
widgets={
'title': forms.TextInput(attrs={'class':'form-control'}),
'category': forms.Select(choices=choices_list, attrs={'class':'form-control'}),
'author': forms.TextInput(attrs={'class':'form-control', 'id':'author'}),
# 'author': forms.Select(attrs={'class':'form-control'}),
'text': forms.Textarea(attrs={'class':'form-control','placeholder':choices_list}),
}
class EditForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'text')
widgets={
'title': forms.TextInput(attrs={'class':'form-control'}),
'text': forms.Textarea(attrs={'class':'form-control','placeholder':"less than 500 words"}),
# 'author': forms.Select(attrs={'class':'form-control'})
}
views.py
class createarticleview(CreateView):
template_name='posts/addpost.html'
model = Post
form_class = PostForm
#fields = '__all__'
# fields = ('title','text') for certain fields
def get_context_data(self, *args, **kwargs):
cat_menu = Category.objects.all()
context = super(createarticleview, self).get_context_data(*args, **kwargs)
context['cat_menu'] = cat_menu
return context
addpost.html
{%extends 'index.html'%}
{%block content%}
{% if user.is_authenticated %}
<div class="container">
<h3>add post...!!!.{{user.username}}</h3>
<br>
<div class="mb-3">
<form method="POST"> {% csrf_token%}
{{form.as_p}}
<button type="submit" class="btn btn-info"> post</button>
</form>
</div>
</div>
<script>
var name = "{{user.username}}";
if(document.getElementById("author").value=name)
document.getElementById('author').readOnly = true;
</script>
{% else%}
<h3>you are not logged in</h3>
{%endif%}
{%endblock content%}
Firstly, Always use PascalCase while defining the name of class, like you can give CreateArticleView rather than createarticleview.
you haven't given choices while defining your model that is Post and given models.CharField().
Update your Post model with choices attribute.
Try this:
models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
CATEOGRY_TYPES = (
('sp', 'sport'),
('te', 'technology'),
('bu', 'business')
)
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('detail', args=[str(self.id)])
# return reverse('home')
class Post(models.Model):
title = models.CharField(max_length=100)
text = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
edited = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
category = models.CharField(
choices=CATEOGRY_TYPES, max_length=2, default='coding')
def __str__(self):
return f'{self.title} by {self.author} {self.pk}'
def get_absolute_url(self):
return reverse('detail', args=[str(self.id)])
# return reverse('home')
views.py
from django.shortcuts import render
from .models import Post, Category
from .forms import PostForm
from django.views.generic.edit import CreateView
class CreateArticleView(CreateView):
template_name = 'posts/addpost.html'
model = Post
form_class = PostForm
success_url = '/success/'
def success(req):
return render(req, 'posts/success.html')
Rest of things will be remain same.
You can do it without reverse method by direclty making ForeignKey field in your Post model.
you can also do this:
models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=100)
text = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
edited = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(
Category, on_delete=models.CASCADE, default='coding')
def __str__(self):
return f'{self.title} by {self.author} {self.pk}'
views.py
from django.shortcuts import render
from .models import Post, Category
from .forms import PostForm
from django.views.generic.edit import CreateView
class CreateArticleView(CreateView):
template_name = 'posts/addpost.html'
model = Post
form_class = PostForm
success_url = '/success/'
def success(req):
return render(req, 'posts/success.html')
Your forms.py can be remain same.
Remember: choices while defining models will be given more preference than ForeignKey.
I know it might be a duplicate and or such a simple thing to ask here. Please assist if you know how to and ask for more details to help me sort my problem. DISCLAIMER: I am a django beginner.
I have a model which has a foreignkey field. I'd like to have the options passed into the foreignkey field display in checkboxes. I have not been able to get it work. These are what I have
class Students(models.Model):
school = models.ForeignKey(School, on_delete=models.CASCADE)
name = models.CharField(max_length=200,unique=True,help_text="Student's name")
form = models.ForeignKey(Form, on_delete=models.CASCADE)
class Form(models.Model):
school = models.ForeignKey(School, on_delete=models.CASCADE)
form = models.CharField(max_length=20)
All the options in form model are to be passed as check boxes in Students model
My forms.py is shown below
class StudentsForm(forms.ModelForm):
def __init__(self, school, *args, **kwargs):
super(StudentsForm, self).__init__(*args, **kwargs)
self.fields['form'] = forms.ModelChoiceField(
queryset=Form.objects.filter(school=school))
class Meta:
model = Students
fields = ("name",'form')
My views.py
class StudentView(LoginRequiredMixin,CreateView):
model = Students
form_class = StudentsForm
template_name = 'add_student.html'
success_url = reverse_lazy('students')
def get_form_kwargs(self):
kwargs = super(StudentView, self).get_form_kwargs()
kwargs['school'] = self.request.user.school
return kwargs
Could I have the options as checkboxes, please???
I would recommend using the default modelchoicefield for foreignkey relations which will give you a select box in your template. Javascript is not my best programming language but here's an answer on how to set this up:
models.py
from django.db import models
class School(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Students(models.Model):
school = models.ForeignKey(School, on_delete=models.CASCADE)
name = models.CharField(max_length=200, unique=True, help_text="Student's name")
def __str__(self):
return self.name
forms.py
from django import forms
from .models import Students
class StudentsForm(forms.ModelForm):
school = forms.CharField(widget=forms.HiddenInput)
class Meta:
model = Students
fields = ("name", "school")
views.py
from django.urls import reverse_lazy
from django.views.generic import FormView
from .forms import StudentsForm
from .models import Students, School
class StudentView(FormView):
template_name = 'add_student.html'
model = Students
form_class = StudentsForm
success_url = reverse_lazy('students')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['schools'] = School.objects.all()
return context
add_student.html
<form>
{{ form.as_p }}
<ul class="school__list">
{% for school in schools %}
<li class="school__list-item">{{ school.name }}
<input type="checkbox" class="school__list-item-checkbox"
value="{{ school.id }}" name="school"/>
</li>
{% endfor %}
</ul>
</form>
<script>
const schools = document.querySelectorAll('.school__list-item-checkbox');
for (let i = 0; i < schools.length; i++) {
schools[i].addEventListener('click', () => {
{# populate the hidden school field with the value that is checked here. #}
})
}
</script>
I made a booklist where cover image can be uploaded inside Booklist class. For more image I added another class called Bookcover. Now in Views.py how can I send both Booklist and Bookcover's by using BookListView
models.py file is below
from django.db import models
from django.utils import timezone
class Booklist(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length = 100)
cover = models.ImageField(null=True, blank=True, default='default-book.jpg')
description = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
price = models.DecimalField(decimal_places=3, max_digits=100)
def __str__(self):
return self.title
class Bookcover(models.Model):
post = models.ForeignKey(Booklist, default=None, on_delete=models.CASCADE)
covers = models.ImageField(upload_to = 'images/')
def __str__(self):
return self.post.title
here is views.py file
from django.shortcuts import render
from django.views.generic import ListView
from .models import Booklist, Bookcover
def home(request):
return render(request, template_name='home/index.html')
class BookListView(ListView):
model = Booklist
template_name = 'home/index.html'
context_object_name = 'books'
ordering = ['-date_posted']
If you make a ForeignKey, Django automatically will generate a relation in reverse to access - in this case - the related BookCovers for a specific Book. Since you did not specify the related_name=… parameter [Django-doc], the name of this relation is modelname_set, so in this case, bookcover_set.
In the template you can access the book covers of a book with:
{% for book in books %}
{{ book.title }}
{% for cover in book.bookcover_set.all %}
<img src="{{ cover.covers.url }}">
{% endfor %}
{% endfor %}
This will result in an N+1 problem however. You can avoid that by using .prefetch_related(…) [Django-doc]:
class BookListView(ListView):
queryset = Booklist.objects.prefetch_related('bookcover_set')
template_name = 'home/index.html'
context_object_name = 'books'
ordering = ['-date_posted']
I have two applications (blog and category). On the post list template I would like to get the category blog name and description.
I have tried to put the import category model in the blog view, but nothing show up. So I have made two views rendering the same template, but it does not work.
Blog models:
from django.db import models
from django.utils import timezone
from autoslug import AutoSlugField
from category.models import Category
class Post(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE,
default = '')
title = models.CharField(max_length=200)
...
class Meta:
verbose_name = "Post"
verbose_name_plural = "Posts"
ordering = ['created_date']
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
category models:
class Category(models.Model):
name = models.CharField(max_length=200)
slug = AutoSlugField(populate_from='name', default='')
parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.CASCADE)
description = models.TextField(max_length=200)
class Meta:
unique_together = ('slug', 'parent',) # Enforcing that there can not be two
verbose_name_plural = "categories" # categories under a parent with same
# slug
def __str__(self): # __str__ method elaborated later in
full_path = [self.name] # post. use __unicode__ in place of
# __str__ if you are using python 2
k = self.parent
while k is not None:
full_path.append(k.name)
k = k.parent
return ' -> '.join(full_path[::-1])
Blog view:
def post_list(request):
posts = Post.objects.all()
cat_blog = Category.objects.get(pk=1)
context = {
'posts': posts,
'cat_blog': cat_blog
}
return render(request, 'blog/post_list.html', context)
Category view:
def cat_blog(request):
cat_blog = Category.objects.get(pk=1)
return render(request, 'blog/post_list.html', {'cat_blog': cat_blog})
post_list.html:
<div class="section-header text-center">
{% for category in cat_blog %}
<h1>{{ category.name }}</h1>
<p class="tag">{{ category.description }}</p>
{% endfor %}
</div>
<div class="row py-5">
{% for post in posts %}
// This part is fine
{% endfor%}
The post loop is fine. How can't I get the category name and description in my section header?
One URL gives one View gives one template.
You use the View to give context to the template to render.
def post_list(request):
posts = Post.objects.all()
cat_blog = Category.objects.get(pk=1)
context = {
'posts': posts,
'cat_blog': cat_blog
}
return render(request, 'blog/post_list.html', context)
Your url.py file should point to the post_list view.
I have django project with three below models:
models.py
from django.db import models
from django.contrib.auth.models import User
class Album(models.Model):
owner = models.ForeignKey(User)
title = models.CharField(max_length=127)
artist = models.CharField(max_length=63)
release_date = models.DateField()
logo = models.ImageField(blank=True, upload_to='album_logos', default='album_logos/no-image.jpeg')
t_added = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(null=True, blank=True, max_length=63)
class Meta:
ordering = ['-release_date']
def __str__(self):
return self.title
class Song(models.Model):
album = models.ForeignKey(Album, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
# is_favorite = models.BooleanField(default=False)
favorites = models.IntegerField(default=0)
song_file = models.FileField(blank=True, null=True, upload_to='song_files', default='song_files/mektub.mp3')
class Meta:
ordering = ['name']
def __str__(self):
return self.name
class Favorite(models.Model):
user = models.ForeignKey(User)
song = models.ForeignKey(Song)
created = models.DateTimeField(auto_now_add=True)
As you can see from these models many users can favorite many songs. In template, I want to add class to songs which are favorited by authenticated user:
template
<span {% if authenticated user favorited this song %}class="favorited" {% endif %}></span>
My problem is, I don't know how to write "if authenticated user favorited this song" in template. In terminal, I can get this information by this code:
user_favorited_this = song.favorite_set.filter(user=sample_user) and True or False
I couldn't do the same thing in template, since it doesn't support passing argument to filter method. How can I overcome this problem?
A tag filter can do what you want:
If the User.favorite_set.all has something in common with Song.favorite_set.all this means the current user has favorited that song
from django import template
register = template.Library()
# Method 1 for django queryset (Better)
#register.filter
def intersection(queryset1,queryset2):
return queryset1 & queryset2
# Method 2 for queryset or python List
#register.filter
def intersection(queryset1,queryset2):
return list(set.intersection(set(queryset1),set(queryset2)))
html:
{% if request.user.favorite_set.all|intersection:song.favorite_set.all %} class="favorited" {% endif %}
It may just be simpler to do this in the view, and then pass the result to the template as a boolean flag.
For example:
def song_view(request):
...
song = Song.objects.get(pk=request.GET['song_id'])
is_favorite = song.favorite_set.filter(user=request.user).exists()
return render(request, 'my_template.html', {'is_favorite': is_favorite})
or for a generic class based view:
class SongDetail(DetailView):
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
song = self.get_object()
is_favorite = song.favorite_set.filter(user=self.request.user).exists()
context['is_favorite'] = is_favorite
return context
and then the code in your template can be simplified to:
<span {% if is_favorite %}class="favorited" {% endif %}></span>