My Django 2 app using Django Channels has a ThreadView or a page that displays a messaging thread between two users, with URL
re_path(r"^(?P<username>[\w.#+-]+)", chat_views.ThreadView.as_view(), name='thread'),
There is a text input form at the bottom of the page to submit a new message via request.POST which is handled via the ThreadView.
I think the problem is arising when I try to put a request.POST delete form (DeleteView) on the page as well in order to delete the thread, URL
path('<int:pk>/delete/', chat_views.ThreadDeleteView.as_view(), name='thread_delete'),
When the delete form is submitted, I get the error
DoesNotExist at /messages/2/delete/ User matching query does not exist.
With these lines being cited at causing the error (from views.py and models.py)
self.object = self.get_object()
obj, created = Thread.objects.get_or_new(self.request.user, other_username)
user2 = Klass.objects.get(username=other_username)
The first line comes from the def post function on the ThreadView, and I'm not sure why that's being triggered.
The thread isn't being deleted as when I go back to inbox, it's still there.
I'm a beginner and may be getting this totally wrong so would appreciate any feedback.
views.py
class InboxView(LoginRequiredMixin, ListView):
template_name = 'chat/inbox.html'
context_object_name = 'threads'
def get_queryset(self):
return Thread.objects.by_user(self.request.user).exclude(chatmessage__isnull=True).order_by('-timestamp')
class ThreadView(LoginRequiredMixin, FormMixin, DetailView):
template_name = 'chat/thread.html'
form_class = ComposeForm
success_url = '#'
def get_queryset(self):
return Thread.objects.by_user(self.request.user)
def get_object(self):
other_username = self.kwargs.get("username")
obj, created = Thread.objects.get_or_new(self.request.user, other_username)
if obj == None:
raise Http404
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
thread = self.get_object()
user = self.request.user
message = form.cleaned_data.get("message")
ChatMessage.objects.create(user=user, thread=thread, message=message)
return super().form_valid(form)
class ThreadDeleteView(DeleteView):
model = Thread
success_url = reverse_lazy('chat:inbox')
models.py
class ThreadManager(models.Manager):
def by_user(self, user):
qlookup = Q(first=user) | Q(second=user)
qlookup2 = Q(first=user) & Q(second=user)
qs = self.get_queryset().filter(qlookup).exclude(qlookup2).distinct()
return qs
# method to grab the thread for the 2 users
def get_or_new(self, user, other_username): # get_or_create
username = user.username
if username == other_username:
print(other_username)
return None, None
# looks based off of either username
qlookup1 = Q(first__username=username) & Q(second__username=other_username)
qlookup2 = Q(first__username=other_username) & Q(second__username=username)
qs = self.get_queryset().filter(qlookup1 | qlookup2).distinct()
if qs.count() == 1:
return qs.first(), False
elif qs.count() > 1:
return qs.order_by('timestamp').first(), False
else:
Klass = user.__class__
user2 = Klass.objects.get(username=other_username)
if user != user2:
obj = self.model(
first=user,
second=user2
)
obj.save()
return obj, True
return None, False
class Thread(models.Model):
first = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_first')
second = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_second')
updated = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
objects = ThreadManager()
def __str__(self):
return f'{self.id}'
#property
def room_group_name(self):
return f'chat_{self.id}'
def broadcast(self, msg=None):
if msg is not None:
broadcast_msg_to_chat(msg, group_name=self.room_group_name, user='admin')
return True
return False
thread html page
<!-- Delete Thread -->
<form action="{% url 'chat:thread_delete' thread.id %}" method='POST'>
{% csrf_token %}
<button type='submit' class='btn btn-light' value='delete'>
<i class="fas fa-trash-alt" style="color:royalblue">
</i>
</button>
</form>
<!-- text input / write message form -->
<form id='form' method='POST'>
{% csrf_token %}
<input type='hidden' id='myUsername' value='{{ user.username }}' />
{{ form.as_p }}
<center><button type="submit" class='btn btn-success disabled' value="Send">Send</button></center>
</form>
urls.py
app_name = 'chat'
urlpatterns = [
path('', chat_views.InboxView.as_view(), name='inbox'),
re_path(r"^(?P<username>[\w.#+-]+)", chat_views.ThreadView.as_view(), name='thread'),
path('<int:pk>/delete/', chat_views.ThreadDeleteView.as_view(), name='thread_delete'),
Related
I have just started learning Django and was trying to create a simple blog website, but for some reason, I am unable to update my blog though UpdateView. Every time I hit the submit button from the update blog page, it takes me back to the detail view page without making any changes. Apart from UpdateView, rest seems to work fine like CreateView, DeleteView, ListView.
Here is the code for views.py-
class PostDetailView(DetailView):
model = Post
template_name = 'posts/post.html'
context_object_name = 'obj'
form = CommentForm
def get_object(self):
obj = super().get_object()
if self.request.user.is_authenticated:
PostView.objects.get_or_create(user=self.request.user,post=obj)
return obj
def get_context_data(self, *args, **kwargs):
CatCount = Category_Count()
context = super().get_context_data(*args, **kwargs)
context['latest'] = Post.objects.order_by('-date_added')[0:3]
context['Category_Count'] = CatCount
context['form'] = self.form
return context
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
post = self.get_object()
form.instance.Author = request.user
form.instance.post = post
form.save()
return redirect('post', pk=post.pk)
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
success_url ="/"
def get_object(self):
obj = super().get_object()
if self.request.user.is_authenticated:
PostView.objects.get_or_create(user=self.request.user,post=obj)
return obj
def form_valid(self,form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post=self.get_object()
if self.request.user == post.Author:
return True
return False
class PostDeleteView( LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url='/'
def test_func(self):
post=self.get_object()
if self.request.user == post.Author:
return True
else:
return False
class PostCreateView( LoginRequiredMixin, CreateView):
model = Post
template_name = 'posts/Create.html'
form_class = PostForm
def form_valid(self, form):
form.instance.Author = self.request.user
return super().form_valid(form)
Here is the form, I am using in UpdateView -
class PostForm(forms.ModelForm):
Content = forms.CharField(widget=TinyMCEWidget(
attrs={'required': False, 'cols': 30, 'rows': 10}
))
class Meta:
model = Post
fields= ['title', 'thumbnail', 'Content','Categories', 'featured']
Here is the template for UpdateView-
{% extends 'posts/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class='col-100 offset-1 mb-6 mt-4'> <h3> Update </h3>
</div>
<div class='col-4 offset-2 mb-5 mt-5'>
{{form.media}}
<form method ='POST' action ='.' enctype = 'multipart/form-data'>
{% csrf_token %}
{{form|crispy}}
<button class="btn btn-primary" type="submit">Submit</button>
</form>
</div>
{% endblock content %}
Here is the link to the project, just in case- https://github.com/k-ken-source/DailyBlog-/blob/master/posts/views.py
I have no idea what am I doing wrong, so any hint would be highly appreciated. ThankYou in advance.
Found out, that the problem lies in your template post_form.html:
<form method='POST' action='.'></form>
Here the action='.' causes you the trouble. This advises the browser to send the data to a different URL, but you need to send the data to the URL of your UpdateView.
Remove this attribute and it works like intended.
I have two apps, here we will call them blog and comments.
Comments has a Comment model. Blog has a blog Model. Comments has a CommentForm. Blog has a DetailView.
I want my CommentForm to appear on by Blog DetailView, so people can submit comments from the blog detail page.
The form renders OK - it makes a POST request, it redirects to get_success_url() but (I've added a couple of prints to views.py - see below) in testing in views.py to see if the form data is received I see the form.is_valid() path is not met, and I don't understand why.
I'm essentially trying to follow this, the 'alternative better solution':
https://docs.djangoproject.com/en/2.2/topics/class-based-views/mixins/#using-formmixin-with-detailview
blog/views.py
class CommentLooker(SingleObjectMixin, FormView):
template_name = 'blogs/blog_detail.html'
form_class = CommentForm
model = blog
def get_object(self):
#self.team = get_object_or_404(team, team_id=self.kwargs['team_id'])
#queryset_list = blog.objects.filter(team = self.team)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
return get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super(CommentLooker, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse('blogs:teams')
class blogDisplay(View):
def get(self,request,*args,**kwargs):
view = blogFromteamContentView.as_view()
return view(request, *args, **kwargs)
def post(self,request,*args,**kwargs):
view = CommentLooker.as_view()
return view(request,*args,**kwargs)
class blogFromteamContentView(LoginRequiredMixin, DetailView):
model = blog
template_name = 'blogs/blog_detail.html'
# override get_object so we can use blog_id when we use this class in urls.py
# otherwise DetailViews expect 'pk' which defaults to the primary key of the model.
def get_object(self):
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
return get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
def get_context_data(self, **kwargs):
context = super(blogFromteamContentView, self).get_context_data(**kwargs)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
# get the list of blogs for a given blog id and team id combination.
context['queryset'] = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
# get and set things related to ability to associate comments to a blog.
initial_data = {
"content_type": blog.get_content_type,
"object_id": blog.blog_id
}
comments = blog.comments # uses the #property set in this class.
comment_form = CommentForm(self.request.POST or None, initial=initial_data)
if comment_form.is_valid():
print(comment_form.cleaned_data)
else:
print('invalido!')
context['comment_form'] = comment_form
return context
blog/models.py
class Blog(models.Model):
team= models.ForeignKey(Team, on_delete=CASCADE)
blog_id = models.AutoField(primary_key=True)
blog_name = models.CharField(
max_length=100, verbose_name='Blog Name')
blog/urls.py
path('teams/<int:team_id>/blogs/<int:blog_id>/', blog.blogDisplay.as_view(), name='detail'),
blog_detail.html
<div>
<p class="lead"> Comments </p>
<form method="POST" action="."> {% csrf_token %}
{{ comment_form}}
<input type="submit" value="Post Comment" class="btn btn-primary">
</form>
<hr/>
{% for comment in blog.comments.all %}
<blockquote class="blockquote">
<p>{{ comment.content }}</p>
<footer class="blockquote-footer"> {{ comment.user }} | {{ comment.timestamp|timesince }} ago </footer>
</blockquote>
<hr/>
{% endfor %}
comments/forms.py
from django import forms
class CommentForm(forms.Form):
content_type = forms.CharField(widget=forms.HiddenInput)
object_id = forms.IntegerField(widget=forms.HiddenInput)
parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
content = forms.CharField(widget=forms.Textarea)
edit:
after using print(comment_form.errors): object_idEnter a whole number.
suggesting my initial_data might be the problem. In fact both content_type and object_id in my initial_data were problems. I was asking for blog.blog_id - I.e. using the class, not an instance. So I changed
get_context_data:
def get_context_data(self, **kwargs):
context = super(blogFromteamContentView, self).get_context_data(**kwargs)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
# get the list of blogs for a given blog id and team id combination.
context['queryset_list_recs'] = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
instance = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
initial_data = {
"content_type": instance.get_content_type,
"object_id": blog_id_
}
and to my views.py:
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
comment_form = CommentForm(self.request.POST)
if comment_form.is_valid():
print('valido')
c_type = comment_form.cleaned_data.get("content_type")
content_type = ContentType.objects.get(model=c_type)
obj_id = comment_form.cleaned_data.get('object_id')
content_data = comment_form.cleaned_data.get("content")
new_comment, created = Comment.objects.get_or_create(
user = self.request.user,
content_type = content_type,
object_id = obj_id,
content = content_data
)
else:
print('postinvalido!')
return super(CommentLooker, self).post(request, *args, **kwargs)
This (inappropriate print statements aside) now appears to give intended behaviour.
after using print(comment_form.errors):
object_id
List item
Enter a whole number.
suggesting my initial_data might be the problem. In fact both content_type and object_id in my initial_data were problems. I was asking for blog.blog_id - I.e. using the class, not an instance. So I changed
get_context_data:
def get_context_data(self, **kwargs):
context = super(blogFromteamContentView, self).get_context_data(**kwargs)
team_id_ = self.kwargs.get("team_id")
blog_id_ = self.kwargs.get("blog_id")
# get the list of blogs for a given blog id and team id combination.
context['queryset_list_recs'] = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
instance = get_object_or_404(blog, blog_id=blog_id_, team=team_id_)
initial_data = {
"content_type": instance.get_content_type,
"object_id": blog_id_
}
and to my views.py:
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
comment_form = CommentForm(self.request.POST)
if comment_form.is_valid():
print('valido')
c_type = comment_form.cleaned_data.get("content_type")
content_type = ContentType.objects.get(model=c_type)
obj_id = comment_form.cleaned_data.get('object_id')
content_data = comment_form.cleaned_data.get("content")
new_comment, created = Comment.objects.get_or_create(
user = self.request.user,
content_type = content_type,
object_id = obj_id,
content = content_data
)
else:
print('postinvalido!')
return super(CommentLooker, self).post(request, *args, **kwargs)
This (inappropriate print statements aside) now appears to give intended behaviour. I'm unclear why an instance of CommentForm needs to be created inside the post method - it feels like I'm doing something wrong here.
I'm writing what should be a very simple todo app. The problem is that the edit view is giving me fits! I'm trying to populate a form with data from the database, and it's just not doing the right thing. I've tried the info from this page, but the translation into class-based views must have broken something, or I'm just not using the right kind of form.
Here's the code for the model:
class Todo(models.Model):
id = models.AutoField(primary_key=True)
todo = models.CharField(max_length=255, unique=True)
todo_detail = models.TextField(default='')
date_created = models.DateField(default=timezone.now())
estimated_completion = models.DateTimeField(default=timezone.now())
maybe_completed = models.BooleanField("Completed?", default=False)
def __unicode__(self):
return self.todo
The view code, the commented out bit is from the link:
class TodoEditView(FormView):
model = Todo
form_class = TodoEditForm
template_name = 'todo_edit.html'
#def get(self, request, *args, **kwargs):
# form = self.form_class()
# form.fields['todo'].queryset = Todo.objects.get(id=self.kwargs['pk'])
# form.fields['todo_detail'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# form.fields['date_created'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# form.fields['estimated_completion'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# form.fields['maybe_completed'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# template_vars = RequestContext(request, {
# 'form': form
# })
# return render_to_response(self.template_name, template_vars)
def get_context_data(self, **kwargs):
context = super(TodoEditView, self).get_context_data(**kwargs)
context['todo'] = Todo.objects.get(id=self.kwargs['pk'])
return context
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
todo = request.POST['todo']
todo_detail = request.POST['todo_detail']
estimated_completion = request.POST['estimated_completion']
date_created = request.POST['date_created']
t = Todo(todo=todo, todo_detail=todo_detail,
estimated_completion=estimated_completion,
date_created=date_created)
t.save()
return redirect('home')
The form code:
class TodoEditForm(forms.ModelForm):
class Meta:
model = Todo
exclude = ('id', )
And the template code:
{% extends 'todos.html'%}
{% block content %}
<form method="post" action="{% url 'add' %}">
<ul>
{{ form.as_ul }}
{% csrf_token %}
</ul>
{{todo.todo}}
</form>
{% endblock %}
What the heck am I doing wrong?
You should use an UpdateView, not a FormView. That will take care of prepopulating your form.
Also note you don't need any of the logic in the post method - that is all taken care of by the generic view class.
I am attempting to save simple data based on a button submission. But i keep getting following error:
TypeError at /exercise/1/
save() got an unexpected keyword argument 'commit'
models.py:
class StrategyHistory(models.Model):
user = models.ForeignKey(User)
strategy = models.ForeignKey(Strategies)
created = models.DateTimeField(editable=False)
modified = models.DateTimeField()
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
self.created = datetime.datetime.today()
self.modified = datetime.datetime.today()
return super(StrategyHistory, self).save(*args, **kwargs)
def __unicode__(self):
return self.strategy.name
views.py:
def exercise_view(request, pk):
template_name = 'mobileApp/page/exercise.html'
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('mobile_user_login'))
strategy = Strategies.objects.get(pk=pk)
context = {
'strategy':strategy,
}
if request.method == 'POST':
strategyhistory = StrategyHistory()
entry = strategyhistory.save(commit=False)
entry.user = self.request.user
entry.strategy = Strategies.objects.get(id=pk)
entry.save()
if pk < 5:
return HttpResponseRedirect(reverse('mobile_exercise', kwargs={'pk':pk+1}))
else:
return HttpResponseRedirect(reverse('mobile_comeback_later'))
return render_to_response(template_name,
context,
context_instance = RC( request, {} ))
html-file:
<form action="" method="post">{% csrf_token %}
Prøv igen
<button type="submit" value="submit" data-inline="true" data-transition="flow" data-icon="check" data-theme="b">Fortsæt</button>
</form>
I am running Django 1.6.2
Your view use StrategyHistory model incorrect. That is the use of forms. This is correct.
def exercise_view(request, pk):
template_name = 'mobileApp/page/exercise.html'
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('mobile_user_login'))
strategy = Strategies.objects.get(pk=pk)
context = {
'strategy':strategy,
}
if request.method == 'POST':
strategyhistory = StrategyHistory.objects.create(
user=request.user,
strategy=strategy)
if pk < 5:
return HttpResponseRedirect(reverse('mobile_exercise', kwargs={'pk': pk + 1}))
else:
return HttpResponseRedirect(reverse('mobile_comeback_later'))
return render_to_response(template_name,
context,
context_instance = RC( request, {} ))
Also you cannot use self.request in that view. There is no self declared.
May be this is what you want:
class StrategyHistory(models.Model):
user = models.ForeignKey(User)
strategy = models.ForeignKey(Strategies)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.strategy.name
Building a form to let users submit posts as well as an image. My model has a text field, image, publish date, and author field. Publish Date and Author auto save without any user input. It works on the admin side with admin.py. However, when displaying this through a view, it is not working. Why?
#models.py
class Question(models.Model):
text = models.CharField(max_length = 500)
image = models.ImageField(upload_to = 'movie_poster')
pub_date = models.DateTimeField(auto_now_add = True)
author = models.ForeignKey(User)
def __unicode__(self):
return self.title
class QuestionForm(ModelForm):
class Meta:
model = Question
exclude = ('author', 'pub_date')
#views.py
def add_question(request):
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect("/home/")
else:
form = QuestionForm
return render_to_response("qanda/add_question.html", {'form': form}, context_instance = RequestContext(request))
#add_question.html
{% block content %}
<div class="loginform">
<h1> Add Movie:</h1>
<form enctype = "multipart/form-data" action = "" method = "post">{% csrf_token %}
{{ form.as_p }}
<input type = "submit" value = "Add" />
<input type = "hidden" name = "next" value = "{{ next|escape }}" />
</form>
{% endblock %}
save_model is not magic that works everywhere just by being there. We'll need to customize the form:
class QuestionForm(ModelForm):
def save(self, user=None, force_insert=False, force_update=False, commit=True):
q = super(QuestionForm, self).save(commit=False)
q.author = user
if commit:
q.save()
return q
class Meta:
model = Question
exclude = ('author', 'pub_date')
def add_question(request):
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
if form.is_valid():
form.save(user=request.user)
return HttpResponseRedirect("/home/")
else:
form = QuestionForm()
return render_to_response("qanda/add_question.html", {'form': form}, context_instance=RequestContext(request))