I'm building a simple messaging application where users can send message to each other .
I'm working a function which allows to users to send messages they previously saved as draft .
The problem is , It doesn't raise any error when no input is submitted or username doesn't exist . I think something is blocking it which mean , it wouldn't create a message to send to other user
this is my model
class Thread(models.Model):
subject = models.CharField(max_length=100, blank=True)
user = models.ForeignKey(User)
class Message(models.Model):
user = models.ForeignKey(User, related_name='sender')
recipient = models.ForeignKey(User, related_name='recipient')
created = models.DateTimeField(auto_now_add=True)
body = models.CharField(max_length=1000)
read = models.BooleanField(default=False)
trash = models.BooleanField(default=False)
sentmessage = models.BooleanField(default=False)
thread = models.ForeignKey(Thread,blank=True,null=True)
def __unicode__(self):
return self.body
because you require a subject , recipient and a body to send a message and they are each in different models and this is when the user has already created the message and saved it as a draft . I have created 2 forms each with different models and populated each form with their objects.
class DraftForm(forms.ModelForm):
recipient = forms.CharField(required=True,max_length=2)
body = forms.CharField(widget=forms.Textarea,required=True,max_length=1)
hidden_field = forms.CharField(widget=forms.HiddenInput())
class Meta:
model = Message
fields = ('body',)
def clean_recipient(self):
recipient = self.cleaned_data['recipient']
try:
recipient = User.objects.get(username=recipient)
except User.DoesNotExist:
raise forms.ValidationError("This username does not exist")
return recipient
class ThreadForm(forms.ModelForm):
class Meta:
model = Thread
fields = ('subject',)
views
#login_required
def ReadDraft(request,id):
try:
messages = Message.objects.get(pk=id,recipient=request.user,trash=True)
except Message.DoesNotExist:
return HttpResponseRedirect(reverse('world:Display'))
thread = Thread.objects.get(message=messages)
initial = {}
initial.update({'hidden_field': messages.id})
draft = DraftForm(instance=messages,initial=initial)
thread = ThreadForm(instance=thread)
person = Person.objects.get(user=request.user)
if request.method =='POST':
id = request.POST.get('hidden_field', False)
form = ThreadForm(request.POST)
forms = DraftForm(request.POST)
if form.is_valid and forms.is_valid():
m = Message.objects.get(pk=id)
recipient = form.cleaned_data['recipient']
subject = form.cleaned_data['subject']
body = form.cleaned_data['body']
message = Message.objects.create(user=request.user,recipient=recipient,body=message,thread=m.thread)
return render(request,'create.html',{'DraftForm':draft,'ThreadForm':thread,'person':person})
forms
{% if DraftForm and ThreadForm %}
<form method="POST" >{% csrf_token %}
{{DraftForm.recipient}}
{{ThreadForm.subject}}
{{DraftForm.body}}
{{DraftForm.hidden_field}}
<input type = "submit" value= "send" class="save" id="send" />
</form>
{% endif %}
{{ThreadForm.subject.errors}}
{{DraftForm.recipient.errors}}
{{DraftForm.body.errors}}
You do not include your forms in your template context when you doing a POST request and validation fails. So there is no error message to your form.
You should do something like:
if request.method =='POST':
# some more code here ...
thread = ThreadForm(request.POST)
draft = DraftForm(request.POST)
Hope this helps.
EDIT: Also look at Burhans answer, you forgot the braces if forms.is_valid()!
Its not working because here:
if form.is_valid and forms.is_valid()
One is a property and one is a method call, and only one of them is actually doing anything (the other is returning True).
Even if there were any errors, you don't have a condition to check if the forms fail and return the template (there is no else clause for your if check).
Putting all this together, you should have something like this:
if request.method =='POST':
id = request.POST.get('hidden_field') # get will return None as default value
form = ThreadForm(request.POST)
forms = DraftForm(request.POST)
if form.is_valid() and forms.is_valid():
# do stuff
return redirect(reverse('some-url'))
else:
ctx = {'DraftForm': form, 'ThreadForm': forms}
return render(request, 'forms.html', ctx)
else:
return redirect(reverse('some-url'))
In the forms.html, you should have:
{{ DraftForm.errors }}
{{ ThreadForm.errors }}
Related
Account is my AUTH_USER_MODEL and AccountDisplayInfo consists of all the additional display info of every account. So they can input and submit, and subsequently update their information. These are my codes, but I'm unsure why it isn't working. First of all, I am receiving this error:
DoesNotExist at /account/5/displayinfo/ AccountDisplayInfo matching query does not exist.
Secondly, the "update" function isn't working.
models.py
class Account(AbstractBaseUser):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
class AccountDisplayInfo(models.Model):
account = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
instagram = models.CharField(max_length=50, unique=True, blank=True, null=True) #instagram
.html
<form method="POST" enctype="multipart/form-data">{% csrf_token %}
{{ form.as_p }}
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-primary btn-sm col-lg-5">Update</button>
</div>
</form>
views.py
def display_information_view(request, *args, **kwargs):
user_id = kwargs.get("user_id")
account = Account.objects.get(pk=user_id)
context = {}
displayinfo = AccountDisplayInfo.objects.get(account=account)
if request.POST:
form = DisplayInformationForm(request.POST, request.FILES, instance=request.user)
if form.is_valid():
info = form.save(commit=False)
info.account = request.user
info.save()
messages.success(request, 'Your profile display information have been updated', extra_tags='editdisplayinfo')
return redirect("account:view", user_id=account.pk)
else:
form = DisplayInformationForm(request.POST, instance=request.user,
initial={
"instagram": displayinfo.instagram,
}
)
context['form'] = form
else:
form = DisplayInformationForm(
initial={
"instagram": displayinfo.instagram,
}
)
context['form'] = form
return render(request, "account/displayinfo.html", context)
forms.py
class DisplayInformationForm(forms.ModelForm):
class Meta:
model = AccountDisplayInfo
fields = ('instagram')
Also, would be great if you can advise on this::
If I have 2 tables. Table 1 and Table 2. Table 2 has a foreign key to table 1 but table 1 dont have a foreign key to table 2. How can I query table 2's data from table 1? Thanks
By default .get() will return a DoesNotExist exception if no object matches the query you executed and stop the code from running, so if you want to input it manually on the same page use filter instead:
displayinfo = AccountDisplayInfo.objects.filter(account=account).first()
Then in your template do something like this:
{% if displayinfo %}
... show display info...
{% else %}
<p> No info yet </p> <!-- (or show some form) -->
{% endif %}
To answer your other question:
You have to use the related_name or related models attribute to access the ForeignKey data or use the model name with the _set suffix, for example:
class Post(models.Model):
title = models.CharField(max_lenght=10)
class Comment(models.Model):
body = models.CharField(max_lenght=200)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
then you would get the Post and its comments:
post = Post.objects.get(pk=1)
comments = post.comments.all()
if you didn't have the related_name attribute in your model field you would do this instead:
comments = post.comment_set.all()
UPDATE
Maybe the issue is in your Form class, try removing the save method from it and instead do this in your view:
if request.POST:
form = DisplayInformationForm(request.POST, request.FILES, instance=request.user)
if form.is_valid():
info = form.save(commit=False)
info.account = request.user
messages.success(request, 'Your profile display information have been updated', extra_tags='editdisplayinfo')
info.save()
return redirect("account:view", user_id=account.pk)
So my model, form, and view are working mostly. View works and sending the email works. The "message" is saved but I cannot get the message_to and message_from to save. It is supposed to save the usernames. I can get everything to save, but cannot get the message saved to the database WITH the to and from usernames. I am trying to only have 1 field in the message. "Content". The to and from should be hidden and auto-populated. I appreciate any other set of eyes on this. Thank you.
'models.py'
class Message(models.Model):
message_content = models.TextField()
message_to = models.ForeignKey(User, on_delete=models.CASCADE, related_name='message_to')
message_from = models.ForeignKey(User, on_delete=models.CASCADE, related_name='message_from')
date_created = models.DateTimeField(default=timezone.now)
unread = models.BooleanField(default=True)
'forms.py'
class MessageSellerForm(forms.ModelForm):
class Meta:
model = Message
'views.py'
def ad_detail(request, *args, **kwargs):
template_name = 'x_ads/ad_detail.html'
ad = get_object_or_404(Ad, pk=kwargs['pk'])
ad.increment_view_count()
if request.method == 'POST':
message_form = MessageSellerForm(data=request.POST)
message_form.message_from = request.user.username
message_form.message_to = ad.creator.username
if message_form.is_valid():
subject = 'Message about your ad. ' + ad.title
from_email = request.user.email
to_email = ad.creator.email
message = 'You have a message about one of your ads waiting for you!'
send_mail(subject=subject, message=message, from_email=from_email,
recipient_list=[to_email], fail_silently=False)
messages.success(request, your message has been sent.')
message_form.save()
return HttpResponseRedirect(request.path_info)
else:
message_form = MessageSellerForm()
return render(request, template_name, {'ad': ad, 'message_form': message_form})
I think I see what you're trying to do there, but there are other ways that I think will be a bit easier.
https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#the-save-method
You could instead:
# create the django object in memory, but don't save to the database
message = message_form.save(commit=False)
message.message_from = request.user.username
message.message_to = ad.creator.username
# now save it to the database
message.save()
If you do that you won't need the assignments to the message form further up:
message_form.message_from = request.user.username
message_form.message_to = ad.creator.username
EDIT
You might also need to modify your MessageSellerForm to not include the message_from and message_to fields so that validation will work. That's OK because you know that you'll be assigning the right values to those fields after form validation but before saving to the database.
After Submitting the like button data is successfuly updating with the database but after this step it won't redirecting to the successful url. instead of that it is throwing attribute error. If I use HttpResponseRedirect('/album/') instaed of successful url this error is not comming. Please refer this link for the traceback
models.py
Codes in models.py
class VoteManager(models.Manager):
def get_vote_or_unsaved_blank_vote(self,song,user):
try:
return Vote.objects.get(song=song,user=user)
except ObjectDoesNotExist:
return Vote.objects.create(song=song,user=user)
class Vote(models.Model):
UP = 1
DOWN = -1
VALUE_CHOICE = ((UP, "๐๏ธ"),(DOWN, "๐๏ธ"),)
like = models.SmallIntegerField(null=True, blank=True, choices=VALUE_CHOICE)
user = models.ForeignKey(User,on_delete=models.CASCADE)
song = models.ForeignKey(Song, on_delete=models.CASCADE)
voted_on = models.DateTimeField(auto_now=True)
objects = VoteManager()
class Meta:
unique_together = ('user', 'song')
views.py
codes in views.py
class SongDetailView(DetailView):
model = Song
template_name = 'song/song_detail.html'
def get_context_data(self,**kwargs):
ctx = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
vote = Vote.objects.get_vote_or_unsaved_blank_vote(song=self.object, user = self.request.user)
vote_url = reverse('music:song_vote_create', kwargs={'song_id':vote.song.id})
vote_form = SongVoteForm(instance=vote)
ctx['vote_form'] = vote_form
ctx['vote_url'] = vote_url
return ctx
class SongVoteCreateView(View):
form_class = SongVoteForm
context = {}
def get_success_url(self,**kwargs):
song_id = self.kwargs.get('song_id')
return reverse('music:song_detail', kwargs={'pk':song_id})
def post(self,request,pk=None,song_id=None):
user = self.request.user
song_obj = Song.objects.get(pk=song_id)
vote_obj,created = Vote.objects.get_or_create(song = song_obj,user = user)
vote_form = SongVoteForm(request.POST, instance=vote_obj)
if vote_form.is_valid():
new_vote = vote_form.save(commit=False)
new_vote.user = self.request.user
new_vote.save()
return new_vote
song_detail.html
codes in html page.
<form action="{{vote_url}}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ vote_form.as_p }}
<button class="btn btn-primary" type="submit" >Vote</button>
</form>
Error code
Please refer this link for the traceback
'Vote' object has no attribute 'get'
The post method needs to return an HttpResponse, not a Vote object.
But you shouldn't be defining post in the first place. All that code should go in form_valid.
def form_valid(self, form):
user = self.request.user
song_obj = Song.objects.get(pk=self.kwargs['song_id'])
vote_obj, _ = Vote.objects.get_or_create(song = song_obj, user = user)
form.instance = vote_obj
return super().form_valid(form)
Note, you don't need to check is_valid, and you also don't need to set the user as you've already done that in vote_obj.
You are returning Vote object from post method in your SongVoteCreateView view. You should return Response instead. Django doesn't know what to do with model object returned from a view.
I'm following a tutorial from to build a simple blog app with Django.
I have noticed that in the new_entry() view, we need to pass topic_id in agrs when using the reverse function:
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST':
#No data submitted, create a blank form
form = EntryForm()
else:
#POST data submitted; process data
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
new_entry.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
context = {'topic': topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
However, when creating the edit_entry() view (that allows users to edit existing entries), we need to pass topic.id
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != 'POST':
#Initial request, render the form with current entry
form = EntryForm(instance=entry)
else:
#Submit changes, process data
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
context = {'topic':topic, 'entry':entry, 'form':form}
return render(request,'learning_logs/edit_entry.html', context)
Initially I thought this was a mistake so I used args=[topic_id] in both reverse functions and it worked fine
Later, I decided I wanted to add a title to each entry so I made some minor changes to models.py, migrated those changes to the database and then changed the templates to include {{entry.title}} in them.
Basically, all I did was add this code to models.py
title = models.CharField(max_length=200, default='Add a title')
models.py:
class Topic(models.Model):
"""A topic the user is learning about"""
text = models.CharField(max_length = 200)
date_added = models.DateTimeField(auto_now_add = True)
def __str__(self):
"""Return a string representation of the model"""
return self.text
class Entry(models.Model):
"""A blog post about a particular topic"""
topic = models.ForeignKey(Topic)
title = models.CharField(max_length=200, default='Add a title')
text = models.TextField()
date_added = models.DateTimeField(auto_now_add = True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Return a string representation of the model"""
char_numb = len(self.text)
if char_numb > 50:
return self.text[:50] + "..."
else:
return self.text
forms.py:
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text','title']
labels = {'text':'', 'title': ''}
widgets = {'text': forms.Textarea(attrs={'cols': 80})}
After adding these changes, I got the following error when I tried to edit an entry's default title:
NameError at /edit_entry/4/
global name 'topic_id' is not defined
I changed args=[topic_id] to args=[topic.id] in the views.py file edit_entry() view and now it works fine, any idea why this is the case? What difference is there between topic_id and topic.id in this context?
This is the edit_entry.html template in case it makes any difference:
{% extends "learning_logs/base.html" %}
{% block content %}
<h1>{{topic}}
</h1>
<p>Edit your entry</p>
<form action = "{% url 'learning_logs:edit_entry' entry.id %}" method
= 'post'>
{% csrf_token %}
{{ form.as_p }}
<button name = "submit">save changes</button>
</form>
{% endblock content %}
Thanks in advance for any advice
In your first view, you have topic_id from the url and you fetch topic from the database on the first line, so you can use either topic_id or topic in the view.
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
In the template context for they view, you set topic but not topic_id. Therefore you can only use topic.id in the template.
context = {'topic': topic, 'form': form}
In your second view, you get entry_id from the url and get topic via the entry. You donโt set topic_id anywhere so you must use topic.
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
I'm trying to create a custom send email application using the User-model.
I am able to get the application functioning by sending email but it does not raise an error on the template , If the user doesn't exist.
So I tried to test whether it will raise any error at all if the number of characters is breach and no error raise . Can someone help me
class Thread(models.Model):
subject = models.CharField(max_length=100, blank=True)
user = models.ForeignKey(User)
class Message(models.Model):
user = models.ForeignKey(User, related_name='sender')
recipient = models.ForeignKey(User, related_name='recipient')
created = models.DateTimeField(auto_now_add=True)
body = models.CharField(max_length=1000)
read = models.BooleanField(default=False)
trash = models.BooleanField(default=False)
sentmessage = models.BooleanField(default=False)
thread = models.ForeignKey(Thread)
def __unicode__(self):
return self.body
views.py
#login_required
def Create(request):
form = NewMessageForm()
if request.method =='POST':
form = NewMessageForm(request.POST)
if form.is_valid():
recipient = form.cleaned_data['recipient']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
thread = Thread.objects.create(subject=subject,user=request.user)
recipient = User.objects.get(username=recipient)
message = Message.objects.create(user=request.user,recipient=recipient,body=message,thread=thread)
return HttpResponseRedirect(reverse('world:message'))
else:
return HttpResponseRedirect(reverse('world:Create'))
return render(request,'create.html',{'messages':messages,'form':form})
forms
class NewMessageForm(forms.Form):
recipient = forms.CharField(required=True,max_length=1)
subject = forms.CharField(required=True,max_length=1)
message = forms.CharField(widget=forms.Textarea,required=True,max_length=1)
def clean_recipient(self):
recipient = self.cleaned_data['recipient']
try:
recipient = User.objects.get(username=recipient)
except User.DoesNotExist:
raise forms.ValidationError("This username does not exist")
return recipient
template
<form method="POST" >{% csrf_token %}
{{form.recipient}}
{{form.subject}}
{{form.message}}
<input type = "submit" value= "send" class="save" id="send"/>
</form>
{{form.recipient.errors}}
{{form.subject.errors}}
{{form.message.errors}}
Your pattern is slightly wrong:
if request.method =='POST':
form = NewMessageForm(request.POST)
if form.is_valid():
recipient = form.cleaned_data['recipient']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
thread = Thread.objects.create(subject=subject,user=request.user)
recipient = User.objects.get(username=recipient)
message = Message.objects.create(user=request.user,recipient=recipient,body=message,thread=thread)
return HttpResponseRedirect(reverse('world:message'))
else:
form = NewMessageForm()
Idea is that you first check if it's a POST, ok - is it working? Great- return correct flow - otherwise fall-through and pass form with errors attached by is_valid() to context. In case it's a new one - create it as a last resort since it doesn't hold any information yet.
Also don't forget form.non_field_errors since it will contain errors that are not specific to any field.