DeleteView with 2 arguements post and user - python

I have a delete view with 2 conditions "post" and "user".
The user requirement is fulfilled by self.object.user = self.request.user and post requirement is fulfilled by slug = self.kwargs['slug'](I think this may be the culprit)
Are my views correct?
I am new to python please forgive any silly mistakes.
Views.py
class ProofDelete(LoginRequiredMixin, DeleteView):
model = Proof
def delete(self, *args, **kwargs):
return super().delete(*args, **kwargs)
def get_success_url(self, *args, **kwargs):
slug = self.kwargs['slug']
print(slug)
obj = get_object_or_404(Post, slug=slug)
url_ = obj.get_absolute_url()
user = self.request.user
if user.is_authenticated():
if user in obj.made.all():
obj.made.remove(user)
else:
obj.made.add(user)
return url_
models.py
User = get_user_model()
class Proof(models.Model):
user = models.ForeignKey(User, related_name='proofmade')
post = models.ForeignKey(Post, related_name='proofmade')
made_at = models.DateTimeField(auto_now=True)
image_of_proof= models.ImageField()
proof_ = models.ImageField()
suggestions = models.TextField(max_length=1000)
def __str__(self):
return self.post.title
urls.py
app_name = 'proof'
urlpatterns = [
url(r'^new_proof/(?P<slug>[-\w]+)/$', views.ProofCreate.as_view(), name='new_proof'),
url(r'^proof_delete/(?P<pk>\d+)/$', views.ProofDelete.as_view(),name='proof_delete'),
also tried
url (r'^proof_delete/(?P<slug>[-\w]+)/(?P<pk>\d+)/$', views.ProofDelete.as_view(), name='proof_delete'),
I get the below error. Indicating error in the views
Error Message Click to see Error message
Same error message after scrolling down. Click to open

The answer was in the templates. Adding a forloop after the "if" statement solved the issue. Doing this handles both arguments of the Url. "slug" of "post" model and "pk" of the "proof" model. Without the forloop It was almost impossible to satisfy both arguments of the URL
The correct Url thanks to "Paulo Almeida"
url (r'^proof_delete/(?P<slug>[-\w]+)/(?P<pk>\d+)/$', views.ProofDelete.as_view(), name='proof_delete')
Below is the template
{% if user in post.made.all %}
{% for proof in user.proofmade.all %}
<a href="{% url 'proof:proof_delete' slug=post.slug pk=proof.pk %}">
<img src="{% static 'images/thumbs_up_RED.png' %}" height="25px">
</a><br/>
{% endfor %}
{% else %}
<a href="{% url 'proof:new_proof' slug=post.slug %}">
<img src="{% static 'images/thumbs_up_BLANK.png' %}" height="25px">
</a><br/>
{% endif %}

Related

Creating simple like button

Hi im developping a blog app and i am creating a simple like button for the post idintified by it's pk where the link is located in a form but it ran into an error.
NoReverseMatch at /single_post/8/
Reverse for 'like_post' not found. 'like_post' is not a valid view function or pattern name.
my views.py for the detail view and the like button view
def post_detail(request, pk):
post = Post.objects.get(id=pk)
context = {
'post': post,
}
return render(request, 'news/post_detail.html', context)
def LikeView(request, pk):
post = get_object_or_404(Post, id=request.POST.get('post_id'))
post.likes.add(request.user)
return(HttpResponseRedirect(reverse('single_post', args=[str(pk)] )))
models.py
class Post(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
image = models.ImageField(upload_to='img/', default='img/default.jpg')
author = models.CharField(max_length=100)
date = models.DateTimeField(auto_now_add=True)
credit = models.URLField(blank=True, null=True)
likes = models.ManyToManyField(User, related_name='daily_posts')
def __str__(self):
return self.title
in the detail views the form and the link
<form action="**{% url 'like_post' post.pk %}**" method="POST">
{% csrf_token %}
<button class="btn btn-primary btn-sm" type="submit", name="post_id", value="{{ post.id }}">Like</button>
</form>
and the error i run to everytime i hit like.
NoReverseMatch at /single_post/8/
Reverse for 'like_post' not found. 'like_post' is not a valid view function or pattern name.
i cannot identify what seems to be the issue here anyone can help please?
In LikeView, you reverse 'single_post' but in the form you have 'like_post'. Perhaps change 'single_post' in LikeView to 'like_post'? EDIT FOR NAMESPACE: I believe you need to include the namespace in the form i.e. {% url 'news:like_post' post.pk %}

How to add comments on Python Django posts with logged in username?

Please let me know that where i am making mistake?
views.py
class AddComment(LoginRequiredMixin, CreateView):
model = Comment
form_class = CommentForm
template_name = 'comment.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
form.instance.name = self.request.user
form.instance.post_id = self.kwargs\['pk'\]
return super().form_valid(form)
Are these forms written correctly?
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('body', )
widgets = {
# 'name': forms.TextInput(attrs={'class': 'form-control'}),
'body': forms.Textarea(attrs={'class': 'form-control'}),
}
Should I make any changes in models?
models.py
class Comment(models.Model):
post = models.ForeignKey(Post,
related_name='comments',
on_delete=models.CASCADE)
name = models.ForeignKey(
User,
on_delete=models.CASCADE,
)
body = models.TextField(max_length=240)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s' % (self.post.title, self.name)
This is comment section for letting user to comment on post with showing its own name?
comments.html
{% if not object.comments.all %}
<p>No comments yet...</p>
Add one
{% else %}
Add Comment
<br><br>
{% for comment in object.comments.all %}
<strong>{{ comment.name }} </strong> - <small>{{ comment.date_added }}</small>
<br>
{{ comment.body }}
<br><br>
<hr>
{% endfor %}
{% endif %}
Here is the urls of AddComment class view.
urls.py
path('post/<int:pk>/comment/', AddComment.as_view(), name='comment'),][1]
You did not state clearly what has gone wrong with your code. I would like to give some suggestions. First of all,
{% for comment in object.comments.all %}
...
{% endfor %}
You are putting this block of code inside {% if not object.comments.all %}...{% endif %} so it will not show on template if the comment section is not empty.
Also, this link:
Add Comment
should open a Django template form, where user can actually fill in the comment. After that, on POST request of the form, it will send comment data to the URL you put in action param of the form, as below:
<form action="{% url 'comment' post.pk %}" method="post">
[Comment code here]
</form>
which will link to this URL you provided:
path('post/<int:pk>/comment/', AddComment.as_view(), name='comment'),]
It will be better if you can provide your code in views.py as well to make it easier to track down where it goes wrong.

NoReverseMatch at /post/1/log/ Reverse for 'log-create' with keyword arguments '{'post_id': ''}' not found

I have a Post model with a whole bunch of posts. I also have a log model which has a foreign key field to the Post model. Essentially the Log model stores log entries for the Posts in the Post model (basically Post comments). Everything was going great. I have been using CBV for my post models and I used a CBV to List my log entries. I then added a link to redirect me to the Log CreateView using the following anchor tag:
<a class="btn" href="{% url 'log-create' post_id=logs.post_id %}">Add Entry</a>
When the NoReverse errors started occuring. When I change the log.post_id to 1, the page loads correctly. This leads me to believe that the log.post_id is not returning any value. Another thought that I had was that because this anchor tag was on the LogListView there were multiple log entries so it didn't know which post_id to use. But I used the get_queryset function on this view to make sure that only logs related to a single post are returned. In my mind the log.post_id should work.
My models are:
class Post(models.Model):
title = models.CharField(max_length=100, blank=True)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
overview = models.TextField(blank=True)
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.id})
def __str__(self):
return self.title
class Log(models.Model):
post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE)
log_entry = models.TextField(max_length=500, blank=True)
log_author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
My Views:
from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView, CreateView
from .models import Post, Log
from django.http import HttpResponseRedirect
from django.contrib.auth.mixins import LoginRequiredMixin
class LogListView(ListView):
model = Log
template_name = 'blog/log_entries.html'
context_object_name = 'logs'
ordering = ['-date_posted']
def get_queryset(self):
self.post = get_object_or_404(Post, log=self.kwargs['pk'])
return Log.objects.filter(post=self.post)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(LogListView, self).get_context_data(**kwargs)
# Add in a QuerySet of all images related to post
context['post'] = Post.objects.all()
return context
class LogCreateView(LoginRequiredMixin, CreateView):
model = Log
fields = [
'log_entry'
]
def form_valid(self, form):
form.instance.log_author = self.request.user
post = Post.objects.get(pk=self.kwargs['post_id'])
return super().form_valid(form)
My urls.py
from django.urls import path, include
from . import views
from .views import LogListView, LogCreateView
urlpatterns = [
path('', PostListView.as_view(), name='blog-home'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/log/', LogListView.as_view(), name='log-list'),
path('post/<int:post_id>/log/new/', LogCreateView.as_view(), name='log-create'),
]
And Lastly, my template:
{% extends "blog/base.html"%}
{% block body_class %} home-section {% endblock %}
{% block content %}
<div class="container">
<h2>Log Entries</h2>
{% for log in logs %}
<div class="row">
<article class="content-section">
<div class="article-metadata log-metadata">
<a class="mr-2" href="{% url 'profile' user=log.log_author %}">{{ log.log_author }}</a>
<small class="text-muted">{{ log.date_posted|date:"d F Y" }}</small>
{% if request.user.is_authenticated and request.user == log.log_author %}
<ion-icon name="trash"></ion-icon>
{% endif %}
</div>
<p class="">{{ log.log_entry }}</p>
</article>
</div>
{% endfor %}
<a class="btn" href="{% url 'log-create' post_id=logs.post_id %}">Add Entry</a>
</div>
{% endblock content %}
I think I am correctly passing a parameter to the url. this is evident from when I make post_id=1. But I am not sure I am calling it correctly. Any help on this issue would be great thanks.
UPDATED: I edited my context_object_name in my LogListView to logs to make the for loop less confusing. Essentially I am trying to get one anchor tag at the bottom of all the log entries to redirect to the Add entry page.
I suggest an approach that renders a link only if there are objects available, using the first element:
</article>
</div>
{% if forloop.first %}<a class="btn" href="{% url 'log-create' post_id=log.post.id %}">Add Entry</a>{% endif %}
{% endfor %}
This log.post.id means get id of post object (foreign key) of log object.

django.urls.exceptions.NoReverseMatch for username but arguments are there

django.urls.exceptions.NoReverseMatch
django.urls.exceptions.NoReverseMatch: Reverse for 'for_user' with keyword arguments '{'username': ''}' not found. 1 pattern(s) tried: ['questions/by/(?P<username>[-\\w]+)/$']
i'm getting a no reverse match error however from what i can tell, all the necessary things are there. I have the url which has the username argument which is being given from the html and is being saved into the model and expressed through the view.
im a still a django novice. all help is appreciated.
file structure
usertest - root
-->accounts - appname
-->questions - appname
-->urls.py
-->views.py
-->model.py
templates
-->base.html
urls.py
url(r'by/(?P<username>[-\w]+)/$', views.UserQuestions.as_view(), name="for_user"),
base.html
<nav class="navbar mynav" role="navigation" id="navbar">
<div class="container">
<a class="navbar-brand mynav" href="{% url 'questions:all' %}">WebSiteIcon</a>
<ul class="nav navbar-nav navbar-right">
{% if user.is_authenticated %}
<li>hello {{ question.user }}</li>
<li></li>
<li></li>
<li></li>
<li>Log out</li>
{% else %}
<li>Log in</li>
<li>Sign up</li>
{% endif %}
</ul>
</div>
</nav>
<div class="container mycontent">
model.py
class Question(models.Model):
class Meta:
ordering = ['-date_updated']
user = models.ForeignKey(User, related_name="questions")
question = models.TextField(blank=False, null=False) # unique=True,
question_html = models.TextField(blank=False, null=False)
answer = models.TextField(blank=False, null=False)
answer_html = models.TextField(blank=False, null=False)
date_created = models.DateTimeField(auto_now=True, null=True)
date_updated = models.DateTimeField(auto_now=True, null=True)
slug = models.SlugField(unique=True, default='')
tags = TaggableManager()
def __str__(self):
return self.question
# ^ to display an object in the Django admin site and
# as the value inserted into a template when it displays an object.
def save(self, *args, **kwargs):
self.question_html = misaka.html(self.question)
self.answer_html = misaka.html(self.answer)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse(
"questions:detail",
kwargs={
"slug": self.slug,
"pk": self.pk,
"username": self.user.username,
}
)
views.py
class UserQuestions(generic.ListView):
model = models.Question
template_name = "questions/user_question_list.html"
def get_queryset(self):
try:
self.question_user =
User.objects.prefetch_related("questions").get(
username__iexact=self.kwargs.get("username")
)
except User.DoesNotExist:
raise Http404
else:
return self.question_user.questions.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["question_user"] = self.question_user
return context
{% url 'questions:for_user' username=question.user.username %}
For this url to work, you need question to be in the template context. That means your view should look something like:
def view_question(request):
question = Question.objects.get(text="What is your name?")
return render(request, "template.html", {'question': question})
The error message with keyword arguments '{'username': ''}' suggests that question is missing from the template context.
When I first saw your url tag, I wondered if you were trying to loop through all the questions in a queryset, for example:
{% for question in question_loop %}
{% url 'questions:for_user' username=question.user.username %}
{% endfor %}
That's why I asked whether the url tag was in a loop.
Now that you've expanded your question, it sounds like you want the username of the logged in user. The auth context processor adds user to the template context, so you can use user.username to get the username.
{% url 'questions:for_user' username=user.username %}
However, your base template should probably handle users that are not logged in as well. You can add an if statement to avoid errors for anonymous users.
{% if user.is_authenticated %}
{% url 'questions:for_user' username=user.username %}
{% endif %}

Unit test on custom django admin template

No clue how to deal with this situation.
Recently start unit test with django.
In my project, i have a custom change_list.html to add a button on the admin page.
I'm looking for a unit test which can verify this custom button.
Here is my code in admin.py :
class LocationAdmin(OSMGeoAdmin):
def get_urls(self):
urls = patterns('', url(
r'^import/$',
self.admin_site.admin_view(self.import_process)
))
super_urls = super(LocationAdmin, self).get_urls()
return urls + super_urls
def import_process(self, request):
pass
admin.site.register(Location, LocationAdmin)
This code automaticaly load the template in app/admin/app/app/change_list.html
Which is :
{% extends "admin/change_list.html" %}
{% load staticfiles %}
{% block extrahead %}
<link rel="stylesheet" href="{% static "css/custom_upload.css" %}">
{% endblock %}
{% block object-tools-items %}
{{ block.super }}
<li><a role="button" href="import/" class="btn btn-primary btn-margin-left">Import</a></li>
{% endblock %}
But how about when you want to proove that work's with a unit test?
I'm able to test template when i can use a view and the function render_to_string
Like this :
response = my_view(HttpRequest())
expected_html = render_to_string(
'template.html',
{'arg_key': arg}
)
self.assertEqual(response.content.decode(), expected_html)
But here, i don't figure it out how to call the admin view with the associate template part.
Here a start of what i found to add unit test on admin page.
class MockRequest(object):
pass
class MockSuperUser(object):
def has_perm(self, perm):
return True
request = MockRequest()
request.user = MockSuperUser()
class ModelAdminTests(TestCase):
def setUp(self):
self.site = AdminSite()
def test_admin_import_button_on_location_admin_page(self):
ma = ModelAdmin(Location, self.site)
#Here the correct assert ?
Thank you all.
This might not be a good idea (probably a better alternative: using the test client). But for reference, here is how you can render the changelist_view programmatically directly from the admin method:
from django.contrib.admin import AdminSite, ModelAdmin
from django.test.client import RequestFactory
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.shortcuts import resolve_url
class MockUser:
is_active = True
is_staff = True
def has_perm(self, *args):
return True
url = resolve_url(admin_urlname(MyModel._meta, "changelist"))
request = RequestFactory().get(url)
request.user = MockUser()
class MyAdmin(ModelAdmin):
change_list_template = "path/to/custom/template/if/needed"
admin = MyAdmin(Work, AdminSite())
html = admin.changelist_view(request).rendered_content

Categories