My project is a discussion forum using Django and here are my create and update functions. The method of update_post should provide update functionality but every time I try to update a post it adds a new post. How can I update a resource?
#login_required
def create_post(request):
context = {}
form = PostForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
print("\n\n its valid")
author = Author.objects.get(user=request.user)
new_post = form.save(commit=False)
new_post.user = author
new_post.save()
form.save_m2m()
return redirect("home")
context.update({
"form": form,
"title": "Create New Post"
})
return render(request, "create_post.html", context)
#login_required
def update_post(request):
context = {}
author = Author.objects.get(user=request.user)
form = PostForm(request.POST , instance=author)
if request.method == "POST":
if form.is_valid():
print("\n\n its valid")
new_post = form.save(commit=False)
# new_post.user = author
new_post.save()
form.save_m2m()
return redirect("home")
context.update({
"form": form,
"title": "UpdatePost",
})
return render(request, "update_post.html", context)
In model total there are 4 classes Post , comment , reply and category and this is Post -
class Post(models.Model):
title = models.CharField(max_length=400)
slug = models.SlugField(max_length=400, unique=True, blank=True)
user = models.ForeignKey(Author, on_delete=models.CASCADE)
content = HTMLField()
categories = models.ManyToManyField(Category)
date = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=True)
hit_count_generic = GenericRelation(HitCount, object_id_field='object_pk',
related_query_name='hit_count_generic_relation'
)
tags = TaggableManager()
comments = models.ManyToManyField(Comment, blank=True)
closed = models.BooleanField(default=False)
state = models.CharField(max_length=40, default="zero")
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
def __str__(self):
return self.title
def get_url(self):
return reverse("detail", kwargs={
"slug":self.slug
})
#property
def num_comments(self):
return self.comments.count()
#property
def last_reply(self):
return self.comments.latest("date")
form = PostForm(request.POST , instance=author)
instance feels like it should be passed an instance of Post instead of an instance of Author. I assume that PostForm is a pretty standard ModelForm with model = Post in the Meta.
Related
Im trying to handle the existing name of a Category, so that users wont be allowed to create 2 categories with the same name, but at the moment its taking all categories from the database, not only for the logged-in user. I dont know how and where to implement request.user.
I`m building an inventory app where everyone creates their own categories and adds items.
Thank you.
This is my model:
class Category(models.Model):
user = models.ForeignKey(User, default=1, on_delete=models.CASCADE,
related_name='category', null=True)
name = models.CharField(max_length=100, null=False, blank=False)
slug = models.SlugField(max_length=100)
created_on = models.DateTimeField(auto_now_add=True)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-timestamp']
verbose_name = 'category'
verbose_name_plural = 'categories'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('category_detail', kwargs={'slug': self.slug})
This is my form:
class CategoryForm(forms.ModelForm):
add_category = forms.BooleanField(widget=forms.HiddenInput, initial=True)
class Meta:
model = Category
fields = ['name']
def clean_name(self):
name = self.cleaned_data.get('name')
if (name == ""):
raise forms.ValidationError('This field cannot be left blank')
for instance in Category.objects.all():
if instance.name == name:
raise forms.ValidationError('There is a category with the name ' + name)
return name
This is my view:
#login_required
def index(request):
categories = Category.objects.filter(user=request.user)
items = Item.objects.all()
add_item = ItemForm()
add_category = CategoryForm()
query = None
if request.method == 'POST':
if 'add_item' in request.POST:
add_item = ItemForm(request.POST)
if add_item.is_valid():
form = add_item.save(commit=False)
form.category = get_object_or_404(
Category, name=request.POST.get('category'),
user=request.user)
add_item.save()
return redirect('home')
else:
add_category = CategoryForm(request.POST)
if add_category.is_valid():
category_form = add_category.save(commit=False)
category_form.save()
messages.success(request, f'{name} has been added')
return redirect('home')
Edit Category View
#login_required
def category_edit(request, pk):
category = Category.objects.get(id=pk)
if request.method == 'POST':
form = CategoryForm(request.POST, instance=category)
if form.is_valid():
form.save()
messages.info(request, f'{category.name} has been updated!')
return redirect('home')
else:
form = CategoryForm(instance=category, user=request.user)
context = {
'form': form,
}
return render(request, 'category_edit.html', context)
Ive tried adding user = request.user in the form class, but that resulted in an error Ive tried adding category_form.user = request.user before saving the form but that was still taking names from every other user
Pass the request's user to the form:
class CategoryForm(forms.ModelForm):
add_category = forms.BooleanField(widget=forms.HiddenInput, initial=True)
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
class Meta:
model = Category
fields = ['name']
def clean_name(self):
name = self.cleaned_data.get('name')
if (name == ""):
raise forms.ValidationError('This field cannot be left blank')
qs = Category.objects.filter(user=self.user, name=name)
if self.instance.pk:
# EXCLUDE CURRENT INSTANCE TO ENABLE EDIT
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise forms.ValidationError('There is a category with the name ' + name)
return name
then in the view you need to pass the user:
#login_required
def index(request):
categories = Category.objects.filter(user=request.user)
items = Item.objects.all()
add_item = ItemForm()
add_category = CategoryForm(user=request.user)
query = None
if request.method == 'POST':
if 'add_item' in request.POST:
add_item = ItemForm(request.POST)
if add_item.is_valid():
form = add_item.save(commit=False)
form.category = get_object_or_404(
Category, name=request.POST.get('category'),
user=request.user)
add_item.save()
return redirect('home')
else:
add_category = CategoryForm(user=request.user, data=request.POST)
if add_category.is_valid():
category_form = add_category.save(commit=False)
category_form.save()
messages.success(request, f'{name} has been added')
return redirect('home')
I got a question I did not solved. I try to create a chat with the author of the post. But I got an issue when I tried to catch him directly with form instance:
I've created a form with crispy form without field. All field are passed with form.instance. I really thank you in advance.
#Post detail
class PostDetail(generic.DetailView,FormMixin):
model = Cuisine
context_object_name = 'post'
template_name = 'post_detail.html'
form_class = CreateChannelForm
def get_context_data(self, **kwargs):
context = super(PostDetail, self).get_context_data(**kwargs)
context['cuisine_user'] = Cuisine.objects.get(pk=user_id)
context['form'] = self.get_form()
return context
def form_valid(self, form):
if form.is_valid():
form.instance.post = self.object
form.instance.usera = self.request.user
form.instance.userb = cuisine_user #Here is the error
form.save()
return super(PostDetail, self).form_valid(form)
else:
return super(PostDetail, self).form_invalid(form)
I got private space:
class Channel(models.Model):
usera = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="channel_usera")
userb = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="channel_userb")
post = models.ForeignKey(Cuisine,on_delete=models.CASCADE,related_name="channel_post")
date_created = models.DateTimeField(auto_now_add=True, null=True)
is_active = models.BooleanField('', default=False)
slug = models.SlugField(editable=False,unique=True)
Here is the model of the post:
class Cuisine(models.Model):
#Remplir manuellement
title = models.CharField(max_length=90)
user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='cuisine_user')
image = models.ImageField(upload_to='nutriscore/')
description = models.TextField(max_length=1000, blank=True)
In order to get userb:
def get_context_data(self, **kwargs):
context = super(PostDetail, self).get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def form_valid(self, form):
if form.is_valid():
form.instance.post = self.object
form.instance.usera = self.request.user
form.instance.userb = self.get_object().user #Here is the change
form.save()
return super(PostDetail, self).form_valid(form)
else:
return super(PostDetail, self).form_invalid(form)
I programmed a simple blog after a tutorial and it works so far.
But now I would like to sort the post list by last activity in a post. As soon as a new post is published or a new comment is approved, the post should be on the top of the list. (the way it works in a forum..)
Here is what I have so far...
(It seems that sorting the list by "updated_date" works, but attaching the right datetime to it when approving a comment fails.)
Thanks for your help in advance
models.py
class Post(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.DO_NOTHING,)
title = models.CharField(max_length=200)
text = RichTextUploadingField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
updated_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey('blog.Post', related_name='comments', on_delete=models.DO_NOTHING,)
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
Post.updated_date = timezone.now()
self.save()
def __str__(self):
return self.text
def approved_comments(self):
return self.comments.filter(approved_comment=True)
views.py
def post_list(request):
posts = post.objects.filter(updated_date__lte=timezone.now()).order_by('-updated_date')
return render(request, 'blog/post_list.html', {'posts': posts})
blog.views.post_detail
#login_required
def post_publish(request, pk):
post = get_object_or_404(Post, pk=pk)
post.publish()
return redirect('post_detail', pk=pk)
def add_comment_to_post(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post_detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/add_comment_to_post.html', {'form': form})
#login_required
def comment_approve(request, pk):
comment = get_object_or_404(Comment, pk=pk)
comment.approve()
return redirect('post_detail', pk=comment.post.pk)
You can try like this with aggregation:
from django.db.models import Max
Post.objects.filter(comments__approved_comment=True).annotate(max_activity=Max('comments__created_date')).order_by('max_activity', 'updated_date')
FYI approved_comments and approve method inside Comment model class won't work. They should be like this:
def approved_comments(self):
return self.__class__.objects.filter(approved_comment=True)
def approve(self):
self.approved_comment = True
self.post.updated_date = timezone.now()
self.post.save()
self.save()
In Comment.approve() you are attaching the updated_date to the class of the Post rather than the instance related to the comment. You should use self.post when updating the date:
def approve(self):
self.approved_comment = True
self.post.updated_date = timezone.now()
self.save()
Writing my first "real" django project I realized that my views are really huge. I try to convert it into CBV (as additional django training). Here are my fbv views:
def index(request):
context = {}
categories = Category.objects.select_related()
subcategories = SubCategory.objects.all()
context['categories'] = categories
context['subcategories'] = subcategories
return render(request, 'mainapp/index.html', context)
def category(request, category_name_slug):
context = {}
try:
category = Category.objects.get(slug=category_name_slug)
subcategory = SubCategory.objects.filter(category=category)
context['subcategories'] = subcategory
context['category'] = category
except Category.DoesNotExist:
raise Http404("Category doesn't exist")
return render(request, 'mainapp/category.html', context)
def subcategory(request, category_name_slug, subcategory_name_slug):
context = {}
form = SiteAddForm(initial={'url': 'http://'})
context['form'] = form
try:
category = Category.objects.get(slug=category_name_slug)
subcategory = SubCategory.objects.filter(category=category
).get(slug=subcategory_name_slug)
sites = Site.objects.filter(subcategory=subcategory, is_active=True)
context['subcategory'] = subcategory
context['category'] = category
context['sites'] = sites
except (SubCategory.DoesNotExist, Category.DoesNotExist):
raise Http404("Nie ma takiej strony")
if request.method == 'POST':
form = SiteAddForm(request.POST)
if form.is_valid():
siteurl = form.cleaned_data['url']
context['siteurl'] = siteurl
try:
page = AddNewSite(siteurl)
except:
print('ERRROR')
context['Error'] = "URL is not responding"
return render(request, 'mainapp/subcategory.html', context)
title = page.title()
description = page.description()
keywords = page.keywords()
if page.check_url_in_database():
context['Error'] = "Site already in database"
return render(request, 'mainapp/subcategory.html', context)
else:
form_extended = SiteAddFormFull(initial={'url': siteurl, 'name':
title, 'description':
description, 'keywords':
keywords})
context['form_extended'] = form_extended
if request.method == 'POST':
form_extended = SiteAddFormFull(request.POST)
if form_extended.is_valid():
keywords = form_extended.cleaned_data['keywords']
name = form_extended.cleaned_data['name']
description = form_extended.cleaned_data['description']
page.add_site(category, subcategory, keywords,
description, name, siteurl)
context['added_site'] = 'Thank you - site was added'
messages.add_message(request, messages.SUCCESS, 'Dodano!!')
return render(request, 'mainapp/subcategory.html', context)
How Can I simpy write my base view (index) using CBV. It should look that way:
Category:
Sub1
Sub2
Category2:
Sub2a
Sub2b
Should I learn CBV or stay using FBV (it works)? Below are my Category, Subcategory models:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True,
verbose_name='Category name')
slug = models.SlugField()
image = models.ImageField(upload_to='category_images',
verbose_name="Category image",
blank=True)
description = models.TextField(default='Description',
verbose_name="Category description")
class Meta:
verbose_name_plural = "Categories"
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def image_thumb(self):
if self.image:
return '<img src="/media/%s" width="40" height="40" />' % (self.image)
else:
return('')
image_thumb.short_description = 'Thumb'
image_thumb.allow_tags = True
def __str__(self):
return self.name
class SubCategory(models.Model):
category = models.ForeignKey(
'Category',
related_name='subcategory',
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=30)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(SubCategory, self).save(*args, **kwargs)
class Meta:
verbose_name_plural = "Subcategories"
def __str__(self):
return self.name
I'm an advocate of CBV, it can make life a lot easier when trying to reuse code. There are also a ton of pre-defined generic views that only need a small tweaks for most instances. For your example index, you can use a TemplateView:
class IndexView(TemplateView):
template_name = "mainapp/index.html"
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['categories'] = Category.objects.select_related()
context['subcategories'] = SubCategory.objects.all()
return context
i'm having some trouble with forms in django 1.5.
i'm trying to write a form that saves new comments on my database.
But it doesn't matter what i write in the comment textspace, it's always considered null
this is my model
class Comment(BaseModel):
auction_event = models.ForeignKey(AuctionEvent, related_name='comments')
commenter = models.ForeignKey(User, related_name='comments')
comment = models.CharField(max_length=200, default='commento', null=True, blank=True)
def __unicode__(self):
return u'Placed on %s by %s' % (self.auction_event.item.title, self.commenter.username)
this is the form
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['comment']
def __init__(self, data=None, auction_event=None, commenter=None, *args, **kwargs):
self.auction_event = auction_event
self.commenter = commenter
super(CommentForm, self).__init__(data, *args, **kwargs)
def clean_comment(self):
cleaned_data = self.cleaned_data
cleaned_comment = cleaned_data.get('comment',Decimal('0.00'))
def clean(self):
cleaned_data = self.cleaned_data
current_time = timezone.now()
if current_time > self.auction_event.end_time:
raise ValidationError('This auction event has expired.')
return cleaned_data
def save(self, force_insert=False, force_update=False, commit=False):
comment = super(CommentForm, self).save(commit=False)
comment.auction_event = self.auction_event
comment.commenter = self.commenter
comment.save()
self.auction_event.save()
return comment
and in the end this is my view
#login_required
def view_comment_history(request, auction_event_id):
try:
auction_event = AuctionEvent.objects.get(pk=auction_event_id)
except AuctionEvent.DoesNotExist:
raise Http404
comments = auction_event.comments.all()
if request.method == 'POST':
form = CommentForm(data=request.POST, auction_event=auction_event, commenter=request.user.user,)
if form.is_valid():
comments = form.save()
return HttpResponseRedirect(request.get_full_path())
else:
form = CommentForm()
return render_to_response('lebay/view_comment_history.html', {
'auction_event': auction_event,
'form': form,
'comments': comments,
}, context_instance=RequestContext(request))
anyone knows why?
You need to return your cleaned comment.
def clean_comment(self):
cleaned_data = self.cleaned_data
cleaned_comment = cleaned_data.get('comment',Decimal('0.00'))
return cleaned_comment