I have a problem when saving/creating new post for blog.
I already have Post model and each post has it's own category. so I have Category model too. In CreateView template I already got all categories from DB and displayed in select/option tag. The issue is I want to save category_id when I create new post. But I don't know how? How can I say if form POSTED get category_id and save it for Post model that has category_id field?
View:
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all().order_by('id')
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
Model:
class Category(models.Model):
title = models.CharField(max_length=255)
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=255)
short_text = models.TextField()
long_text = models.TextField()
post_pic = models.ImageField(default="post_pic.jpg",
blank=False, upload_to='post_pics')
date_published = models.DateTimeField(default=timezone.now())
date_upadated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, default=None,
on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})`
post_form.html
{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title_head %}
New Post
{% endblock title_head %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4 pb-2">New Post</legend>
{{ form|crispy }}
<select class="form-control" name="category">
{% for category in categories %}
<option value="{{ category.id }}">{{ category }}</option>
{% endfor %}
</select>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-outline-info">Post</button>
</div>
</form>
{% endblock content %}
There are a few ways to achieve this. Here is the simplest:
If you expect the Catagories to remain the same then you can hard code them and use a CharField with the choices= keyword on your Post model.
class Post(models.Model):
CATEGORY_CHOICES = (
('Horror', 'Horror'),
('Romance', 'Romance'),
('Comedy', 'Comedy'),
)
category = models.CharField(choices=CATEGORY_CHOICES)
Then all you need to do is add 'category' to your fields list in the CreateView.
Side Note You should probs rename your CreateView it's a view not a form. This may cause confusion.
Solved!
we have access category in list of fields so it's useless to fetch all categories then displaying them in select-option tag in html page. just add category field in list of fields
Change PostCreateForm
change it from this
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all().order_by('id')
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
to this
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic', 'category']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
Related
I'm trying to do Django class-based CreateView inline formsets.
I have a product model and a productImage model and productImage is inline
everything looks fine but after i submit my product the images that are selected in formset will not save.
Here is my code
models.py:
class Product(models.Model):
name=models.CharField(max_length=200, db_index=True),
slug=models.SlugField(max_length=200, db_index=True, allow_unicode=True),
description=models.TextField(blank=True),
vector_column=SearchVectorField(null=True, editable=False),
meta={"indexes": (GinIndex(fields=["vector_column"]),)}
category = models.ForeignKey(Category, related_name='products',
on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
def get_absolute_url(self):
return reverse('shop:product_detail', args=[self.id, self.slug])
class ProductImage(models.Model):
product = models.ForeignKey(Product, default=None, on_delete=models.CASCADE)
image = models.ImageField(upload_to='products/%y/%m/%d')
def __str__(self):
return self.product.name
views.py
class ProductCreate(StaffAccessMixin, ProductFormValidMixin, ProductFieldsMixin,
CreateView):
model = Product
def get_context_data(self, **kwargs):
context = super(ProductCreate, self).get_context_data(**kwargs)
if self.request.POST:
context['product_image_formset'] = ProductImageFormset(self.request.POST)
else:
context['product_image_formset'] = ProductImageFormset()
return context
template_name = 'products/product-create-update.html'
ProductFormValidMixin:
class ProductFormValidMixin():
def form_valid(self, form):
context = self.get_context_data()
product_image_formset = context['product_image_formset']
if self.request.user.is_superuser:
self.obj = form.save()
if product_image_formset.is_valid():
product_image_formset.instance = self.obj
product_image_formset.save()
return redirect('administration:product-update', pk=self.obj.pk)
else:
self.obj = form.save(commit=False)
self.obj.available = False
return super().form_valid(form)
ProductFieldsMixin:
class ProductFieldsMixin():
def dispatch(self, request, *args, **kwargs):
if request.user.is_superuser:
self.fields = ["name",
"slug",
"description",
"category",
"price",
"available",
"image",
]
else:
raise Http404
return super().dispatch(request, *args, **kwargs)
forms.py:
from django.forms.models import inlineformset_factory
from .models import Product, ProductImage
ProductImageFormset = inlineformset_factory(Product, ProductImage, fields=('image',),
extra=3)
Formset template:
<h5 class="text-info">Add Product Metas</h5>
{{ product_image_formset.management_form|crispy }}
{% for form in product_image_formset.forms %}
<tr class="{% cycle 'row1' 'row2' %} formset_row-{{product_image_formset.prefix }}">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field|as_crispy_field }}
</td>
{% endfor %}
</tr>
{% endfor %}
Everything is ok and my product form is saved after submit but the images i select in my form set won't save
Problem solved!
This code will work for other types of data but not for images and files
to solve this problem get_context_data function should be like this:
def get_context_data(self, **kwargs):
context = super(ProductCreate, self).get_context_data(**kwargs)
if self.request.POST:
context['product_image_formset']=ProductImageFormset(self.request.POST,
**self.request.FILES**)
else:
context['product_image_formset'] = ProductImageFormset()
return context
In my first code self.request.FILES was missing :) and because of that images couldn't saved
I am new to Django and as such I have a few gaps in my knowledge, particularly with regards to the get_context_data() in views. My aim is to try and make use of the DRY aspect of Django so that I have a list of categories on my webpage and each category reveals a list of posts that fall under it. To achieve this, I have a ListView and DetailView class (shown below). Note, the categories and posts are all objects of the same model.
My issues are the following:
I would like to display only some of the posts in my ListView not all of them. Is there a dynamic way to do this?
In my DetailView, I want to display a list of posts but from a single choice, for eg say choice1 is selected when creating a post, then only all posts with choice 1 selected will be displayed. I would like to do that dynamically, currently its hardcoded to only display choice1. I have tried to use the get_context_data() to do this but this only outputs a queryset in my template which leads me to problem 3
My HTML file currently displays all posts of choice1 as a QuerySet but not the actual content
Views.py
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
class PostDetailView(DetailView):
model = Post
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data(*args, **kwargs)
list_of_relevant_articles = Post.objects.filter(choice = "choice1")
context.update({'list_of_relevant_articles': list_of_relevant_articles})
return context
Models.py
POST_CHOICES = (
('choice1','CHOICE1'),
('choice2', 'CHOICE2'),
('choice3','CHOICE3'),
('choice4','CHOICE4'),
)
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(blank=True, null=True)
choice = models.CharField(max_length=14, choices = POST_CHOICES, default ='choice1')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog-post', kwargs={'pk': self.pk})
Html file
{% block content %}
<!-- This for loop does not output anything but I believe i need one to iterate through
a queryset-->
{% for post in object_list %}
{{ list_of_relevant_articles }}
{% endfor %}
{% endblock content %}
Any help/advice will be greatly appreciated
1
you can use context['posts'] = context['posts'][:3] and a normal loop in template, but it is better to use pagination in general.
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts'] = context['posts'][:3]
return context
{% for post in posts %}
{{ post }} <br>
{% endfor %}
The other way is do not use get_context_data and inside the template use slicing like:
{% for post in posts|slice:":2" %}
2, 3
class PostDetailView(DetailView):
model = Post
template_name = 'blog/detail.html'
slug_url_kwarg = "choice"
slug_field = "choice"
context_object_name = 'posts'
def get_object(self, queryset=None):
return Post.objects.filter(choice=self.kwargs.get("choice"))
{% for post in posts %}
{{ post }} <br>
{% endfor %}
urls.py
urlpatterns = [
# ...
path('post/', views.PostListView.as_view(), name='list'),
path('post/<str:choice>/', views.PostDetailView.as_view(), name='detail'),
]
so in the browser: .../post/choice2/, .../post/choice3/, ...
In my Blog Django Project I am trying to create a Like Feature, but currently facing the below error:
Reverse for 'post-detail' with no arguments not found. 1 pattern(s) tried: ['blog/(?P<slug>[-a-zA-Z0-9_]+)/$']
What should I do to prevent this error and to return back to the same Post-detail page after pressing the like button?
Here is the urls.py
path('blog/<slug:slug>/', PostDetailView.as_view(), name='post-detail'),
path('blogs/like', like_post, name='like-post'),
Here is the models.py
class Post(models.Model):
liked = models.ManyToManyField(User, default=None, blank=True, related_name='liked')
def num_likes(self):
return self.liked.all().count()
def get_absolute_url(self):
return reverse('blog:post-detail', kwargs={'slug': self.slug})
LIKE_CHOICES = (
('Like', 'Like'),
('Unlike', 'Unlike')
)
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
value = models.CharField(choices=LIKE_CHOICES, default='Like', max_length=10)
date_liked = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.post)
Here is the views:
I have made a couple of trials but did not work, I have commented them in the end of the views.py as they returned like_post() missing 1 required positional argument: 'slug'
class PostListView(ListView):
model = Post
template_name = "blog/post_list.html" # <app>/<model>_<viewtype>.html
ordering = ['-date_posted']
context_object_name = 'posts'
paginate_by = 1
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html" # <app>/<model>_<viewtype>.html
def get(self, request, *args, **kwargs):
res = super().get(request, *args, **kwargs)
self.object.incrementViewCount()
return res
def like_post(request):
user=request.user
if request.method=='POST':
post_id=request.POST.get('post_id')
post_obj= Post.objects.get(id=post_id)
if user in post_obj.liked.all():
post_obj.liked.remove(user)
else:
post_obj.liked.add(user)
like,created=Like.objects.get_or_create(user=user,post_id=post_id)
if not created:
if like.value=='Like':
like.value='Unlike'
else:
like.value='Like'
like.save()
return redirect('blog:post-detail') <------------ Error Showing from Here
#post = get_object_or_404(Post, slug=Post.slug)
#if post.slug != slug:
#return redirect('blog:post-detail', slug=Post.slug)
Here is the template:
<form action="{% url 'blog:like-post' %}" method="POST">
{% csrf_token %}
<input type="hidden" name="post_id" value='{{post.id}}'>
{% if user not in obj.liked.all %}
<button class="ui button positive" type="submit"> Like</button>
{% else %}
<button class="ui button negative" type="submit">Unlike</button>
{% endif %}
<br>
<strong>{{ post.liked.all.count }} Likes</strong>
</form>
redirect('blog:post-detail', slug=post_obj.slug)
My model looks like this:
class ComicSeries(models.Model):
"""Model definition for ComicSeries."""
# TODO: Define fields here
user = models.ForeignKey(User, on_delete=models.CASCADE,
null=True, blank=True, verbose_name='Uploaded by: '
)
title = models.CharField(verbose_name='Series Title', max_length=500)
cover = models.ImageField(verbose_name='Series cover', upload_to='comic_series',
height_field=None, width_field=None, max_length=None
)
description = models.TextField(verbose_name='Description')
artist = models.CharField(verbose_name='Artist(s)', max_length=500)
date_uploaded = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(default='')
class ComicIssue(models.Model):
"""Model definition for ComicIssue."""
# TODO: Define fields here
user = models.ForeignKey(User, on_delete=models.CASCADE,
null=True, blank=True, verbose_name='Uploaded by: '
)
title = models.ForeignKey(ComicSeries, on_delete=models.CASCADE, verbose_name='Series Title')
issue = models.CharField(verbose_name='Issue Number', max_length=500)
issue_title = models.CharField(verbose_name='Issue Title', max_length=1000)
issue_cover = models.ImageField(verbose_name='Issue cover', upload_to='comic_issues', height_field=None, width_field=None, max_length=None)
issue_description = models.TextField(verbose_name='Description')
issue_file = models.FileField(verbose_name='Issue file', upload_to='comic_issues_files', max_length=100,
help_text='File in pdf or as single image'
)
is_favorite = models.BooleanField(default=False)
issue_slug = models.SlugField(default='')
views.py :
class ComicIssueCreate(LoginRequiredMixin, CreateView):
model = ComicIssue
fields = ['issue_title', 'issue_cover', 'issue_description', 'issue_cover', 'issue_file']
def form_valid(self, form):
form.instance.user = self.request.user
return super(ComicIssueCreate, self).form_valid(form)
I am able to select to which ComicSeries a ComicIssue belongs to in Django admin.
In django admin there is an option to upload
But on my form, there is no field when I add 'title'
Template:
{% block body %}
<div class="container">
<h2>Add new comic issue/chapter</h2>
<form class="form", action="", method="POST", enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="form-group form">
<span class="text-danger small">
{{field.errors}}
</span>
</div>
<label class="control-label col-sm-2">
{{field.label_tag}}
{{field.help_text}}
</label>
<div class="col-sm-10">{{field}}</div>
{% endfor %}
<button type="submit" class="btn grey-text black">Add</button>
</form>
</div>
{% endblock body %}
But I have a problem doing this in a custom form. Is there a way I can determine to which series an issue belongs to in a custom form using CreateView?
You missed 'title' in the fields specified in the view. Since title is the foreign key of ComicIssue to ComicSeries, You need to include that in the fields to achieve whats required
class ComicIssueCreate(LoginRequiredMixin, CreateView):
model = ComicIssue
fields = ['title', 'issue_title', 'issue_cover', 'issue_description',
'issue_cover', 'issue_file']
def form_valid(self, form):
form.instance.user = self.request.user
return super(ComicIssueCreate, self).form_valid(form)
Updates:
The problem was due to not initializing the 'select' in MaterializeCSS. It is necessary to select field to work in MaterializeCSS
<script>
$(document).ready(function() {
$('select').material_select();
});
</script>
The fields in ComicIssueCreate should include title
So fields = ['title','issue_title', 'issue_cover', 'issue_description', 'issue_cover', 'issue_file']
I'm writing a django app, and I'd like for users to be able to select a [team_number] from a dropdown menu, then when they hit submit be redirected to a page that renders out the database information associated with that selection. I'm using the redirect class View, but the problem I'm having is that there is no dropdown menu showing up to select [team_number] from on the html page team-stats.html.
views.py:
class TeamStatsView(View):
def get(self, request, *args, **kwargs):
return render(request, 'team-stats.html',
{'team_number': TeamStats()})
def post(self, request, *args, **kwargs):
team_number = TeamStats(request.POST, request.FILES)
if team_number.is_valid():
# do stuff & add to database
team_number.save()
team_number = TeamStats.objects.create()
# use my_file.pk or whatever attribute of FileField your id is
# based on
return HttpResponseRedirect('/team-stats/%i/' % team_number.pk)
return render(request, 'team-stats.html', {'team_number': team_number})
models.py:
class Team(models.Model):
team_number = models.IntegerField()
team_notes = models.CharField(max_length=150)
event_id = models.ForeignKey(
'Event', on_delete=models.CASCADE, unique=False)
def __unicode__(self):
return str(self.team_number)
class Meta:
db_table = 'teams'
app_label = 'frcstats'
forms.py:
class TeamStats(forms.ModelForm):
class Meta:
model = Team
fields = ['team_number']
team-stats.html:
<form method="post" action="">
{% csrf_token %} {{ TeamStatsView }}
<input type="submit" value="Submit" />
</form>
If there are any other files that I need to update into here to show what I'm trying to do, please let me know. Thanks
Try changing your view variable name to team_numbers and replacing your team-stats.html snippet with the following:
<form method="post" action="">
<select name="teams">
{% for team_number in team_numbers %}
<option value="{{ team_number }}">Team Num: {{ team_number }}</option>
{% endfor %}
</select>
</form>
Then update your view to:
class TeamStatsView(View):
def get(self, request, *args, **kwargs):
return render(request, 'team-stats.html',
{'team_numbers':Team.objects.values('team_number')})
You can use choices=NUMBERS
NUMBERS = (
('1','1'),
('2','2'),
('3','3'),
('4','4')
)
class Team(models.Model):
team_number = models.IntegerField(choices=NUMBERS )
team_notes = models.CharField(max_length=150)
event_id = models.ForeignKey(
'Event', on_delete=models.CASCADE, unique=False)
def __unicode__(self):
return str(self.team_number)
class Meta:
db_table = 'teams'
app_label = 'frcstats'
Your view variable is called team_number.
Try to change TeamStatsView into team_number:
<form method="post" action="">
{% csrf_token %} {{ team_number }}
<input type="submit" value="Submit" />
</form>