When submitting the button after clicking like it is creating the object successufuly but like field is updating with null value and django is getting error 302. Can somebody please help me with this.
After Edit
I have removed form_valid function from the SongVoteUpdateView now update part is working fine but if I removed form_valid function from SongVoteCreatView It is throwing intigrity error like this NOT NULL constraint failed: album_vote.song_id. For trace back please refer this link, if not removed it will creat blank object with no like.
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(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)
if vote.id:
vote_url = reverse('music:song_vote_update', kwargs={'song_id':vote.song.id,'pk':vote.id}) #'pk':vote.id
else:
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(CreateView):
form_class = SongVoteForm
model = Vote
def get_success_url(self,**kwargs):
song_id = self.kwargs.get('song_id')
return reverse('music:song_detail', kwargs={'pk':song_id})
def form_valid(self, form):
user = self.request.user
song_obj = Song.objects.get(pk=self.kwargs['song_id'])
vote_obj, created = Vote.objects.get_or_create(song = song_obj, user = user)
form.instance = vote_obj
return super(SongVoteCreateView).form_valid(form)
class SongUpdateVoteView(UpdateView):
form_class = SongVoteForm
model = Vote
# def form_valid(self, form):
# user = self.request.user
# song_obj = Song.objects.get(pk=self.kwargs['song_id'])
# vote_obj, created = Vote.objects.get_or_create(song = song_obj, user = user)
# form.instance = vote_obj
# print(form)
# return super().form_valid(form)
def get_success_url(self):
song_id = self.kwargs.get('song_id')
return reverse('music:song_detail', kwargs={'pk':song_id})
urls.py
url mapping
path('album/song/<int:pk>/',views.SongDetailView.as_view(), name='song_detail'),
path('album/song/create/<int:song_id>/',views.SongVoteCreateView.as_view(), name='song_vote_create'),
path('album/song/update/<int:song_id>/<int:pk>/', views.SongUpdateView.as_view(), name='song_vote_update')
song_detail.html
Codes in html page
<div>
<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>
</div>
forms.py
Code in forms.py
class SongVoteForm(forms.ModelForm):
like = forms.ChoiceField(widget=forms.RadioSelect,choices=Vote.VALUE_CHOICE)
class Meta:
model = Vote
fields = ['like',]
error Code
response got in console.
[31/Oct/2019 04:15:19] "GET /album/song/1/ HTTP/1.1" 200 3560
**[31/Oct/2019 04:15:21] "POST /album/song/update/1/8/ HTTP/1.1" 302 0**
[31/Oct/2019 04:15:21] "GET /album/song/1/ HTTP/1.1" 200 3560
I have explicitly mentioned like in the form_valid in SongVoteCreateView as shown below. Now the form is not creating blank object and redirecting to the correct page.
def form_valid(self, form):
like = form.cleaned_data.get('like')
user = self.request.user
song_obj = Song.objects.get(pk=self.kwargs['song_id'])
vote_obj, created = Vote.objects.get_or_create(song = song_obj, user = user, like=like)
form.instance = vote_obj
return super().form_valid(form)
Related
When I use single form there is no problem but when I used multi-forms in a class based view it's getting validation failed with image field. I tried to fix with previous solution provided in stack overflow but couldn't able to solve it.
views.py
codes in views
class ProfileEditView(View):
profile_class = ProfileForm
profile_image_class = ProfileImageForm
template_name = 'user/profile_edit.html'
def get(self,request,pk):
if pk:
user = User.objects.get(pk=pk)
profile = Profile.objects.get(user = user)
profile_image = ProfileImage.objects.get(user = user)
profile_form = self.profile_class(instance = profile)
profile_image_form = self.profile_image_class(instance = profile_image)
context = {
'profile_form':profile_form,
'profile_image_form':profile_image_form
}
return render(request, self.template_name, context)
else:
profile_form = self.profile_class(None)
profile_image_form = self.profile_image_class(None)
context = {
'profile_form':profile_form,
'profile_image_form':profile_image_form
}
return render(request, self.template_name, context)
def post(self,request,pk=None, **kwargs):
profile_form = self.profile_class(request.POST,instance=Profile())
profile_image_form = self.profile_image_class(request.POST,instance=ProfileImage())
if profile_image_form.is_valid(): #and profile_image_form.is_valid():
profile = profile_form.save(commit=False)
profile_image = profile_image_form.save(commit=False)
profile.user = self.request.user
profile_image.user = self.request.user
profile.save()
profile_image.save()
return redirect('music:album_list')
context = {
'profile_form':profile_form,
'profile_image_form':profile_image_form,
'error_message':'Something went wrong',
}
return render(request, self.template_name, context)
models.py
codes in model
def get_profile_upload_to(instance,filename):
new_filename = '{}.{}'.format(uuid4,filename.split('.')[-1])
return "profile/{}/{}".format(instance.user.id, new_filename)
class ProfileImage(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
image = models.ImageField(upload_to=get_profile_upload_to)
uploaded = models.DateTimeField(auto_now=True)
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
bio = models.TextField(max_length=500,null=True, blank=True)
location = models.CharField(max_length=50,null=True, blank=True)
birth_date = models.DateField(null=True, blank=True)
email_confirmed = models.BooleanField(default=False)
form.py
codes in form.py
class ProfileImageForm(forms.ModelForm):
class Meta:
model = ProfileImage
fields = ['image']
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['birth_date']
profile_edit.html
code in html page.
<form method="post" enctype="multipart/form-data">{% csrf_token %}
{{error_message}}
{{profile_form}}
{{profile_image_form}}
<button type="submit">Submit</button>
</form>
Error code
when I print the form.is_valid I got below lines.don't know Why image field is validating false
[21/Oct/2019 08:24:24] "POST /user/profile/46/ HTTP/1.1" 200 2861
profile_form is : True
profile_image_form is : False
profile_form is : <bound method BaseForm.is_valid of <ProfileForm bound=True, valid=True, fields=(birth_date)>>
profile_image_form is : <bound method BaseForm.is_valid of <ProfileImageForm bound=True, valid=False, fields=(image)>>
There are quite a few things wrong here. For example, your profile_form doesn't even seem to be a form.
But your immediate error is that you're not passing the FILES data to the image form. It should be:
profile_image_form = self.profile_image_class(request.POST, request.FILES)
(Note there's no point passing an empty instance, just leave that out.)
Also make sure that in your template the form has the right enctype:
<form action="" method="post" enctype="mulitpart/form-data">
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'),
I have a little problem. I want to to display in list only records belongs to user whos add it. In my app when I'am login as 'user' and I want to add new records incude records from list, I see all records in db from ForeignKey. How to make it correctly?
In 'Strona www' when I expand the list I see all records, not only records added by user personally.
My view for it is:
#login_required
def new_keyword(request):
if request.method == "POST":
new_keyword = KeywordForm(request.POST)
if new_keyword.is_valid():
new_keyword=new_keyword.save(commit=False)
new_keyword.user = request.user
new_keyword.save()
messages.success(request, 'Pomyślnie dodano słowo kluczowe')
return render(request, 'konto/new_keyword_ok.html')
else:
new_keyword = WebsiteForm()
return render(request, 'konto/new_keyword.html', {'new_keyword':new_keyword})
in forms.py I have:
class KeywordForm(forms.ModelForm):
class Meta:
model = Keyword
fields = ('www', 'keyword')
models.py
class Keyword(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Użytkownik")
www = models.ForeignKey(Website, on_delete=models.CASCADE, verbose_name="Strona www")
keyword = models.CharField(max_length=250, verbose_name="Słowo kluczowe", unique=False)
urls.py
path('new-keyword/', new_keyword, name='new_keyword'),
and html for display the form is:
{% if request.user.is_authenticated %}
<form action="." method="post">
{{ new_keyword.as_p }}
{% csrf_token %}
<p><input type="submit" value="Dodaj nowe słowo kluczowe" ></p>
Powrót do monitoringu
</form>
{% endif %}
EDIT:
models.py
from django.db import models
from django.contrib.auth.models import User
class Website(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Użytkownik")
website = models.CharField(max_length=250,verbose_name='Strona www', unique=False)
class Meta:
verbose_name = 'Strona www'
verbose_name_plural = 'Strony www'
def __str__(self):
return self.website
class Keyword(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Użytkownik")
www = models.ForeignKey(Website, on_delete=models.CASCADE, verbose_name="Strona www")
keyword = models.CharField(max_length=250, verbose_name="Słowo kluczowe", unique=False)
class Meta:
verbose_name = 'Słowo kluczowe'
verbose_name_plural = 'Słowa kluczowe'
def __str__(self):
return self.keyword
Pass the request.user to your form and use the inverse relation user.website_set :
forms :
class KeywordForm(forms.ModelForm):
class Meta:
model = Keyword
fields = ('www', 'keyword')
def __init__(self, user, *args, **kwargs):
super(KeywordForm, self).__init__(*args, **kwargs)
self.fields['www'].queryset = user.website_set.all()
self.user = user
def save(self, commit=True):
instance = super(KeywordForm, self).save(commit=False)
instance.user = self.user
if commit:
instance.save()
return instance
views:
#login_required
def new_keyword(request):
if request.method == "POST":
new_keyword = KeywordForm(request.user, request.POST)
if new_keyword.is_valid():
new_keyword.save()
messages.success(request, 'Pomyślnie dodano słowo kluczowe')
# DONT DO THIS ! REDIRECT INSTEAD
return render(request, 'konto/new_keyword_ok.html')
else:
new_keyword = KeywordForm(request.user)
return render(request, 'konto/new_keyword.html', {'new_keyword':new_keyword})
As a side note: after a successful POST you want to redirect (even if to the same url). This avoids a lot of troubles (and duplicate submissions) when a user reloads the page
Just pass the user id to your form and then set a queryset for your field
like this:
How to pass the user to your form:
in your view:
#login_required
def new_keyword(request):
if request.method == "POST":
#### Notice this part.
new_keyword = KeywordForm(data=request.POST, u=request.user)
if new_keyword.is_valid():
new_keyword=new_keyword.save(commit=False)
new_keyword.user = request.user
new_keyword.save()
messages.success(request, 'Pomyślnie dodano słowo kluczowe')
return render(request, 'konto/new_keyword_ok.html')
else:
# Notice this part.
new_keyword = WebsiteForm(data=None, u=request.user)
return render(request, 'konto/new_keyword.html', {'new_keyword':new_keyword})
How to retrieve the user in form and set the queryset:
forms.py:
class KeywordForm(forms.ModelForm):
class Meta:
model = Keyword
fields = ('www', 'keyword')
def __init__(self, current_user, *args, **kwargs):
self.u = kwargs.pop("u") ## The user you just passed.
super(KeywordForm, self).__init__(*args, **kwargs)
self.fields['www'].queryset = Website.objects.filter(user=self.u) ## Setting the queryset.
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 a newbie to Django and this question might be an easy one, but it has already been bothering me for a while.
<form method="post" action="/vote/" class="vote_form">
<input type="hidden" id="id_link" name="user_profile" class="hidden_id" value="5" />
<input type="hidden" id="id_voter" name="voter" class="hidden_id" value="3" />
New Score: <input name="score" class="" value="7" />
<button type="submit">Submit</button>
</form>
urls.py
url(r'^vote/$', auth(VoteFormView.as_view()), name="vote"),
views.py
class VoteFormView(FormView):
form_class = VoteForm
def create_response(self, vdict=dict(), valid_form=True):
response = HttpResponse(json.dumps(vdict))
response.status = 200 if valid_form else 500
return response
def form_valid(self, form):
profile = get_object_or_404(UserProfile, pk=form.data['user_profile'])
user = self.request.user
score = form.cleaned_data['score']
prev_votes = Vote.objects.filter(voter=user, profile=profile)
has_voted = (len(prev_votes) > 0)
ret = {"success": 1, "profile": profile, "voter: ": user}
if not has_voted:
# add vote
v = Vote.objects.create(voter=user, profile=profile, score=score)
ret["voteobj"] = v.id
# else response already ranked
return self.create_response(ret, True)
def form_invalid(self, form):
...do something when form invalid...
forms.py
class VoteForm(forms.ModelForm):
class Meta:
model = Vote
models.py
class Vote(models.Model):
voter = models.ForeignKey(User)
profile = models.ForeignKey(UserProfile)
score = models.FloatField(default=10.0)
def __unicode__(self):
return "%s voted %s" % (self.voter.username, self.profile)
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
# Extra attributes
As you can see, I have a simple form with 2 fields: a user_profile (PK of a user profile) and a score. After the form is submitted, it has this error: "form_errors": {"profile": ["This field is required."]} . So it always goes to to form_invalid.
Where am I supposed to get profile object if it is not form_valid?
I tried to redefine VoteForm as below, which does not work either.
class VoteForm(forms.ModelForm):
def clean_profile(self):
profile_pk = self.cleaned_data['profile']
profile = None
try:
profile = UserProfile.objects.get(pk=profile_pk)
except UserProfile.DoesNotExist:
raise forms.ValidationError("User profile doesn't exist.")
return profile
def save(self, *args, **kwargs):
vote = super(VoteForm, self).save(*args, **kwargs)
return vote
class Meta:
model = Vote
Any ideas?
You have to change your hidden input name from user_profile to profile.
Keep in mind this is a poor design since anyone can inject an arbitrary voter id.
Here is how I would do it
class VoteForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super(VoteForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
# manipulate self.user
and then in your view
form = VoteForm(..., user=request.user)
The name of your hidden field is user_profile, but your code refers to it as just profile. Try changing the html of the form so it is named profile.
As an aside you should include the voter id in your form, that should be added in your processing code based on the logged in user. Currently anyone could change the voter hidden field and record a vote for a different user.