i cant specify a user to access my post in django - django-views

its a bit tricky for i tried solving it but cant,i am using class based views with customuser model, i have a blog post with multiple user i want to make it in a way only the main authour of a post should be allowed to delete or update post but i dont seems to know what to do or how to set it,here is my code kindly take a glance and release me from debugging,instead i get what i want wont get nothing and inatead i get something every author has the right to delete each other's post plus this is how i want it to look like when the user view its blog_detail but for non user it should be blank [![enter image description here][1]][1]
views.py
class BlogUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Blog
fields = ['title', 'categories', 'overview', 'thumbnail', 'summary']
def form_valid(self, form):
form.instance.user = Doctor.objects.get(user=self.request.user)
return super().form_valid(form)
def test_func(self):
blog = Doctor.objects.get(user=self.request.user)
if self.request.user == blog.user:
return True
return False
class BlogDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Blog
success_url = reverse_lazy('blog')
def test_func(self):
blog = Doctor.objects.get(user=self.request.user)
if self.request.user == blog.user:
return True
return False
blog_detail.html
{% extends "pages/base.html" %}
{% load static %}
{% block title %}Jazeera Blog{% endblock title %}
{% block meta %} the best medical-service website in Abuja {% endblock meta %}
{% block content %}
<br><p></p>
<section class="ftco-section bg-light" id="blog-section">
<div class="container">
<div class="row justify-content-center mb-5 pb-5">
<div class="col-md-10 heading-section text-center ftco-animate">
<h2 class="mb-4">Gets Every Single Updates Here</h2>
<p>Far far away, behind the word mountains, far from the countries Vokalia and Consonantia</p>
</div>
</div>
<div class="container">
<div class="row justify-content-center mb-5 pb-5">
<div class="col-lg-8 ftco-animate">
<h2 class="text-center">{{ object.title }}</h2>
<div class="meta mb-3 text-center">
<div><h6><span><a href = "">written By {{ object.user }}</span><small class="date"><i class="icon-clock"></i> {{ object.timestamp|timesince }} ago</small><a/></h6>
</div>
</div>
<div><small class="icon-eye text-danger">{{ object.view_count }}</small></div>
<div class="meta mb-3 text-center">
<h5>{% for cat in object.categories.all %}<span class="btn btn-dark">{{ cat }}</span> {% endfor %}</h5>
</div>
<p class="text-center">{{ object.overview }}</p>
{% if object.created_by == user %}
<a class="btn btn-secondary btn-sm mt mb-2" href="{% url 'blog-update' blog.id %}">Update</a>
<a class="btn btn-danger btn-sm mt mb-2" href="{% url 'blog-delete' blog.id %}">Delete</a>
{% endif %}
</div>
</div>
</div>
</div>
</section>
{% endblock content %}
##urls.py
path('blog/', BlogListView.as_view(), name='blog'),
path('blog/<int:pk>/', BlogDetailView.as_view(), name='blog-detail'),
path('blog/<int:pk>/update/', BlogUpdateView.as_view(), name='blog-update'),
path('blog/new/', BlogCreateView.as_view(), name='blog-create'),
path('blog/<int:pk>/delete/', BlogDeleteView.as_view(), name='blog-delete'),
[1]: https://i.stack.imgur.com/aojDX.jpg

You can restrict the set of Blog objects in the queryset to the ones that are written by the request.user:
class BlogDeleteView(LoginRequiredMixin, DeleteView):
model = Blog
success_url = reverse_lazy('blog')
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
user=self.request.user
)
This will return a HTTP 404 error, if a user aims to remove a Blog object post where the post.user is not the logged in user.

In your views (BlogUpdateView and BlogDeleteView), update test_func to become;
def test_func(self):
blog = Doctor.objects.get(pk=self.kwargs.pk)
return self.request.user == blog.user
If you're testing for one condition in multiple views as you just did, it will be better creating a separate class for the test. Say, IsOwnerMixin where you perform the test and then inherit it in the required view(s).
Additionally, you can change {% if object.user == user %} in your blog_detail.html to become {% if user.is_authenticated and object.user == request.user %}
Good luck!

Related

How to set success url as the previous page after updating an instance in django

I am trying to redirect the user to the previous page after they have updated an instance in the Model Class. So, here is the view for the update:
class ClassStatusDetailView(OrganisorAndLoginRequiredMixin, generic.UpdateView):
model = Class
template_name = "agents/class_status_detail.html"
context_object_name = "class"
fields = ['status']
def get_success_url(self):
return reverse("agents:agent-list")
Right now, as you can see, the get_success_url is set to "agents:agent-list", which is not the previous page. Also, here is the template for the update view in case you need it:
{% extends "base.html" %}
{% load tailwind_filters %}
{% block content %}
<div class="max-w-lg mx-auto">
<a class="hover:text-blue-500" href="#">Something</a>
<div class="py-5 border-t border-gray-200">
<h1 class="text-4xl text-gray-800">{{ class.student }}</h1>
</div>
<form method="post" class="mt-5">
{% csrf_token %}
{{ form|crispy }}
<button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">
Update
</button>
</form>
</div>
{% endblock content %}
However, there is a catch. The previous page I want to return to is a function view with a primary key. So, not only do I have to go back to this function view, but I also have to go to the correct primary key. Please tell me if you guys need any other information. Thank you!
When user successfully update their data then he/she redirect to class_list.html page..
urls.py(I assume):
path('class_list/<int:pk>/', class_list,name = 'class_list'),
path('edit_class/<int:pk>/', ClassStatusDetailView.as_view(),name = 'edit_class')
models.py:
class ClassStatusDetailView(OrganisorAndLoginRequiredMixin, generic.UpdateView):
model = Class
template_name = "agents/class_status_detail.html"
context_object_name = "class"
fields = ['status']
def get_success_url(self):
agent_id = self.object.teacher.id
return reverse_lazy('class_list', kwargs={'pk': agent_id})
Use reverse_lazy

how do i create a profile page that shows all users post to the user and to other users

am new to Django and am trying to create a blog site where users can sign up and post articles, but my problem is how can I display a user's post on the user's profile page so that when other users reading the post clicks on the author's name it takes them to the post.author's profile page with the post.author's recent post listed and not the request.user's post. here is my code.
here is accounts/views.py
views.py
#login_required()
def user_profile_view(request, username):
post = User.objects.filter(courses__author=username)
context = {'courses': courses}
return render(request, 'accounts/user_profile_view.html', context)
and here is post/models.py
models.py
class Posts(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
cover = ProcessedImageField(upload_to='post_featured_image',
processors=[ResizeToFill(600, 300)],
format='png',
options={'quality': 80},
blank=True)
slug = models.SlugField()
title = models.CharField(max_length=200)
body = models.TextField()
summary = models.TextField(max_length=200)
here is the template
post_list.html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="col-lg-6">
{% for course in courses_list %}
<div class="central-meta item">
<div class="user-post">
<div class="friend-info">
{% if post.author.profile.avatar %}
<figure>
<img src="{{ post.author.profile.avatar.url }}" alt="">
</figure>
{% else %}
<img src="{% static 'default.ico' %}" alt="">
{% endif %}
<div class="friend-name">
<ins>{{ course.author.get_full_name|title }}</ins>
<span>published: {{ post.published }}</span>
</div>
<div class="post-meta">
{% if course.cover %}
<img src="{{ post.cover.url }}" alt="">
{% endif %}
<div class="friend-name">
<ins>{{ course.title|title }}</ins>
</div>
<div class="description">
<p><span>{{ post.summary|capfirst }}</span></p>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock content %}
here is my accounts/urls.py
urls.py
app_name = 'accounts'
urlpatterns = [
path('accounts/profile/<str:username>/', user_profile_view, name='user_profile'),
]
you should be able do that, easy and via lots of ways.
make a post model like this:
class Post(models.Model):
author = foreign_key(User, related_name="posts", on_delete=models.CASCADE)
title = char_field()
content ........
so with that related name you will be able to reverse from user to its posts too!.
in your template do this:
{{ post.author }}
this link will redirect user to the posts author dashboard if you arrange the urls pattern correctly.
then for sorting authors posts, you have to pass the dashboard_owner to the template and use this name instead of user which normally will refer to the request.user -> user who are visiting the page
{% for post in dash_owner.posts.all %}
<li> <a href="{{ post.get_abslute_url }}" target="_blink">
{{ post.title }} </a> </li>
{% endfor %}
in views.py
def dashboard(request, owner_id=None)
if owner_id:
dash_owner = get_or_404(User, id=owner_id)
elif request.user.is_authenticated:
dash_owner = User.objectd.get(id=request.user.id)
return render(request, 'dashboard.html', {'dash_owner': dash_owner})
in urls.py -> urlpatterns:
path('dashboard/<int:owner_id>/', views.dashboard, name="dashboard")
this is the idea behind that, but for get the better result, you may need to clearly define the get_absolute_url of User model, to give you the url which machts to
'dashboard/<int:id/'
or another way is instead of that, do this:
{{ post.author }}

Django: How to add comments under post

I have trouble adding comments under my posts on the website I'm creating using Django.
This is my story.html file, which is supposed to show the story title, the story itself, all the comments of the story and give users the ability to add a new comment. Although the form is shown, it is not usable. Even though I have added comments to the stories manually through admin, none of them is shown.
{% extends "pinkrubies/base.html" %}
{% block content %}
<div class="post-preview">
<h2 class="post-title"> {{ story.title }}</h2>
<p class="post-subtitle">
{{ story.story }}
</p>
</div>
<div class="post-preview">
{% for com in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
</div>
{% if user_id %}
<div class="post-preview">
<form action="{% url 'pinkrubies:story' user.id story.id %}" method="post">
{% csrf_token %}
<div class="form-group">
<p class="post-title">
Comments
</p>
<textarea id="text" name="text"class="form-control" placeholder="Comment" rows="4">{{ comment.com }}
</textarea>
</div>
<button type="submit" class="btn btn-primary"> Submit </button>
</form>
</div>
{% else %}
<p class="post-meta">You must have an account to comment. Log in or Register</p>
{% endif %}
{% endblock %}
views.py
def story_view(request, user_id, story_id):
latest_comments = Comment.objects.order_by('-date')
if story_id is not None:
story = get_object_or_404(Story, pk=story_id)
else:
story = Story()
story.user_id = user_id
if request.method == 'POST':
story.title = request.post.get('title')
story.story = request.post.get('story')
story.date = timezone.now()
story.save()
return HttpResponseRedirect(reverse('pinkrubies:story', args=(user_id,)))
else:
context = {
'user_id': user_id,
'story_id': story_id,
'title': story.title,
'story': story,
'comments': story.comments,
'latest_comments': latest_comments
}
return render(request, 'pinkrubies/story.html', context)
def comment_view(request, comment, user_id):
latest_comments = Comment.objects.order_by('-date')
if request.method == 'POST':
comment.com = request.POST['com']
comment.date = timezone.now()
comment.save()
return HttpResponseRedirect(reverse('pinkrubies:story', args=(user_id,)))
else:
context = {'latest_comments': latest_comments}
return render(request, 'pinkrubies/story.html', context)
I am aware I have added the "latest_comments" in both views, I did that to see if any of it works and it doesn't. I'm not even sure that the comment_view is needed.
Also, when I try to submit a comment, this error is thrown:
AttributeError: 'WSGIRequest' object has no attribute 'post'
in story_view story.title = request.post.get('title')
Any help is welcome!
Thank you !!!
First of all, if you want to get something from POST, you should use request.POST.get('title'); although I would rather use a Form and let it handle the request.
At a first glance, this:
{% for com in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
Sould be
{% for comment in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
In your code, you're using com to iterate through latest_comments, yet you try to use comment to access com attribute (not sure how your Comment model looks like though)
It's accessed via request.POST. WSGIRequest object does not have a post field it has POST field.
Also, you are not sending the fields you are attempting to read from request.
...
story.title = request.POST.get('title')
story.story = request.POST.get('story')
...
only field you are sending is called text and it should be accessed like this
text = request.POST.get('text')
also template has more errors please check your code once again.

Django display search results

I'm currently working on a blog app in Django. As all Blog apps I need a search form. Therefore I have write a small view and context processor (to make the search form available globally) that queries search results:
view.py:
class BlogSearchListView(ListView):
model = Post
paginate_by = 10
def get_queryset(self):
qs = Post.objects.published()
keywords = self.request.GET.get('q')
if keywords:
query = SearchQuery(keywords)
title_vector = SearchVector('title', weight='A')
content_vector = SearchVector('content', weight='B')
tag_vector = SearchVector('tag', weight='C')
vectors = title_vector + content_vector + tag_vector
qs = qs.annotate(search=vectors).filter(search=query)
qs = qs.annotate(rank=SearchRank(vectors, query)).order_by('-rank')
return qs
base.html:
<div class="globalsearch">
<form id="searchform" action="{% url 'search' %}" method="get" accept-charset="utf-8">
<label for="{{ categorysearch_form.category.id_for_label }}">In category: </label>
{{ categorysearch_form.category }}
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search for ...">
<button class="searchbutton" type="submit">
<i class="fa fa-search"></i>
</button>
</form>
</div>
settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'quickblog.quickblog.context_processors.categorysearch_form',
)
context_processors.py
from .forms import PostForm
def categorysearch_form(request):
form = PostForm()
return {'categorysearch_form': form}
post_list.html:
{% extends 'quickblog/base.html' %}
{% block content %}
{% for post in posts %}
<div class="post">
<h1><u>{{ post.title }}</u></h1>
<p>{{ post.content|linebreaksbr }}</p>
<div class="date">
<a>Published by: {{ post.author }}</a><br>
<a>Published at: {{ post.published_date }}</a><br>
<a>Category: {{ post.category }}</a><br>
<a>Tag(s): {{ post.tag }}</a>
</div>
</div>
{% endfor %}
<div>
<span>
{% if posts.has_previous %}
« First <a> |</a>
Previous
{% endif %}
{% if posts.has_next %}
<span> Page {{ posts.number }} of {{ posts.paginator.num_pages }}.</span>
Next<a> |</a>
Last »
{% endif %}
</span>
</div>
{% endblock %}
The rest of the project can be checked here: https://github.com/rsmvdl/quickblog
I now want to render the results the same way as they get display in my post_list.html view with one detail more: I want that the word the user searched for gets highlighted in the search results, so that the user is able to get the context of the content ASAP ;) . I hope to get any creative ideas. Thanks.
You can create a custom template filter to add an HTML tag around the searched-for word in your post title or content. This will not be an exact solution but you should be able to adapt it:
#register.filter
#stringfilter
def highlight_search_term(text, search_term):
if search_term in text:
text.replace(search_term, '<span class="highlight">' + search_term + '</span>')
return mark_safe(text)
You can then apply this filter to your post title or content in your template with {{ post.title|highlight_search_term:search_term }}.
You will need to also pass search_term to your template context so that the filter can know what to highlight. Be careful with mark_safe() if you are applying this to user-submitted content!
You can create a context processor and pass request.GET.get('q') as context.
context_processors.py
def search(request):
search_keyword = request.GET.get('q')
context = {'search_keyword': search_keyword}
return context
And in your template access it as {{ search_keyword }}
search_results.html
<h3>Showing results for: {{ search_keyword }}</h3>

Use two forms at the same time

I want to submit two forms in my template. The reason for that is, that I have two separate models, each with a model form, which I have to submit at the same time to create the desired result:
I have these two forms:
class BootstrapModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(BootstrapModelForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control'
})
class DeckForm(ModelForm):
class Meta:
model = Deck
exclude = ('dust',)
class GuideForm(BootstrapModelForm):
class Meta:
model = Guide
exclude = ('author', 'upvotes', 'downvotes', 'release_date', 'modified', 'deck')
def __init__(self, *args, **kwargs):
super(GuideForm, self).__init__(*args, **kwargs)
self.fields['title'].label = 'Title of your AWESOME Guide!'
self.fields['public'].label = 'Check, when you want to make your guide public'
self.fields['public'].widget.attrs.update({
'class': 'checkbox'
})
self.fields['introduction'].label = 'Introduction (not required, but recommended)'
A deck is a part of the Guide that gets created. Since the user should be able to create his own deck while writing his guide, I need two forms.
In my views I handled it like this:
def guide_create(request):
if request.method == "POST":
deck_form = DeckForm(request.POST)
guide_form = GuideForm(request.POST)
if guide_form.is_valid() and deck_form.is_valid():
new_deck = deck_form.save(commit=False)
new_deck.dust = 0
new_deck.save()
new_guide = deck_form.save(commit=False)
new_guide.author = Account.objects.get(id=request.user.id)
new_guide.deck = Deck.objects.get(id=new_deck.id)
new_guide.save()
else:
print(guide_form.errors)
print(deck_form.errors)
else:
deck_form = DeckForm(prefix = 'DeckForm')
guide_form = GuideForm(prefix = 'GuideForm')
return render(request, 'hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
I use commit=False so I still can insert the deck in my guide. Now my problem comes with creating the template. When I submit the forms with my current set up, I receive no errors any more but the guide and the deck aren't saved either!
<div style="width: 60%; margin: 0 auto;">
<form class="form-horizontal" method="POST" action="{% url 'GuideCreate' %}"> {% csrf_token %}
<fieldset>
{% for field in DeckForm %}
{% if field.errors %}
<div class="class-group error">
<label class="control-lable">{{ field.label }}</label>
<div class="controls">{{ field }}
<span class="help-inline">
{% for error in field.errors %}{{ error }}{% endfor %}
</span>
</div>
</div>
{% else %}
<div class="control-group">
<label class="control-label">{{ field.label }}</label>
<div class="controls">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
<fieldset>
{% for field in GuideForm %}
{% if field.errors %}
<div class="class-group error">
<label class="control-lable">{{ field.label }}</label>
<div class="controls">{{ field }}
<span class="help-inline">
{% for error in field.errors %}{{ error }}{% endfor %}
</span>
</div>
</div>
{% else %}
<div class="control-group">
<label class="control-label">{{ field.label }}</label>
<div class="controls">{{ field }}</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
<div class="form-actions" style="margin-top: 4px;">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
</div>
Edit I don't receive an error message any more, the deck gets created as well but the guide that belongs to it not.
Just Change
return render_to_response('hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
to
return render(request,'hsguides/guide_create.html', {
'DeckForm': DeckForm,
'GuideForm': GuideForm,
})
You're saying the guide doesn't get created. I see this line new_guide = deck_form.save(commit=False)..... shouldn't it be new_guide = guide_form.save(commit=False) ?
Not 100% but this might be a mistake. Hopefully, it's not just because you refactored the code for putting it here.
I didnot check the whole thing, but this place is wrong, it should be:
if request.method == "POST":
deck = DeckForm(request.POST)
if deck.is_valid():
# ....
you are not packing the POST data into your forms..
the same goes for the second form as well
Try this, you might forgot to pass the RequestContext:
from django.template import RequestContext
return render_to_response('hsguides/guide_create.html',
{'DeckForm': DeckForm, 'GuideForm': GuideForm},
RequestContext(request))
It passes the csrf token to the template.