Django ModelForm Not Saving to DB - python

I'm new to Django and having a hard time figuring out how to get a model form to save to my db. I've been following some tutorials/books and spending a lot of time on SO and I just can't figure this piece out. The book examples i'm following is creating an IMDB type website where a user can vote movie quality (changed to games for my example).
python v. 3.6.7,
django v. 2.1.3,
postgres v. 2.2.2
Here is the model i'm trying to store and the associated manager
class VoteManager(models.Manager):
def get_vote_or_unsaved_blank_vote(self, game, user):
try:
vote = Vote.objects.get(game=game, user=user)
return vote
except self.model.DoesNotExist:
vote = Vote(game=game, user=user)
return vote
class Vote(models.Model):
objects = VoteManager()
value = models.FloatField()
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
game = models.ForeignKey(Game, on_delete=models.CASCADE,)
voted_on = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('user', 'game')
Now the model form I am trying using to try and store this
class VoteForm(forms.ModelForm):
user = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=get_user_model().objects.all(), disabled=True)
game = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=Game.objects.all(), disabled=True)
value = forms.FloatField()
class Meta:
model = Vote
fields = ('user', 'game', 'value')
Template i'm using to display this information.
{% block main %}
<h1>{{ object }}</h1>
<p class="lead">
{{ object.summary }}
</p>
{% endblock %}
{% block sidebar %}
{# rating div omitted #}
<div>
{% if vote_form %}
<form
method="post"
action="{{ vote_form_url }}" >
{% csrf_token %}
{{ vote_form.as_p }}
<button
class="btn btn-primary" >
Vote
</button >
</form >
<h3>Score: {{ object.score|default_if_none:"No score yet!" }}</h3>
{% else %}
<p >Log in to vote for this game</p >
{% endif %}
</div >
{% endblock %}
Finally the views to combine all of these pieces
class GameDetail(DetailView):
queryset = Game.objects.all_with_related_persons_and_score()
def post(self, request, *args, **kwargs):
return redirect('core:CreateVote', game_id=kwargs['pk'])
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(game=self.object, user=self.request.user)
if vote.id:
vote_form_url = reverse('core:UpdateVote', kwargs={'game_id': vote.game.id, 'pk': vote.id})
else:
vote_form_url = reverse('core:CreateVote', kwargs={'game_id': self.object.id})
ctx['vote_form'] = VoteForm(instance=vote)
ctx['vote_from_url'] = vote_form_url
return ctx
class CreateVote(LoginRequiredMixin, CreateView):
form_class = VoteForm
def get_initial(self):
initial = super().get_initial()
initial['user'] = self.request.user.id
initial['game'] = self.kwargs['game_id']
return initial
def get_success_url(self):
print('never called?')
game_id = self.kwargs['game_id']
return reverse('core:GameDetail', kwargs={'pk': game_id})
def render_to_response(self, context, **response_kwargs):
game_id = self.kwargs['game_id']
game_detail_url = reverse('core:GameDetail', kwargs={'pk': game_id})
return redirect(to=game_detail_url)
My guess currently is that I just don't know how to get form from the template, many examples on here create the form when requested, the tutorial i've been following create the form in get_context_data, then pass it off to the template. So i've been struggling to figure out how to save or validate if the form is correct.
Sorry for all the text, but these pieces are so interconnected I wanted to make sure I included everything that would be useful. Displaying and reading from the db seem to work, if I manually go in and add votes through the admin panel I can read them successfully, when trying to write a new score through the vote button nothing happens, below is what the terminal window outputs.
[12/Dec/2018 20:24:34] "GET /game/85 HTTP/1.1" 200 2081
[12/Dec/2018 20:46:49] "POST /game/85 HTTP/1.1" 302 0
[12/Dec/2018 20:46:49] "GET /game/vote/85/create HTTP/1.1" 302 0
[12/Dec/2018 20:46:49] "GET /game/85 HTTP/1.1" 200 2081
Oh maybe the url patterns might be useful.
app_name = 'core'
urlpatterns = [
path('', views.MainPage.as_view(), name='MainPage'),
path('games', views.GameList.as_view(), name='GameList'),
path('game/<int:pk>', views.GameDetail.as_view(), name='GameDetail'),
path('game/vote/<int:game_id>/create', views.CreateVote.as_view(), name='CreateVote'),
path('game/vote/<int:game_id>/update/<int:pk>', views.UpdateVote.as_view(), name='UpdateVote'),
]
Thanks in advance, i've been beating my head against the wall on this one for a while.

Try adding type="submit" to your button:
<button class="btn btn-primary" type="submit">Vote</button >
and then in your CreateView add a form_valid method:
class CreateVote(LoginRequiredMixin, CreateView):
model = Vote
template_name = 'folder/create_vote.html'
form_class = VoteForm
...
def form_valid(self, form):
vote = form.save(commit=False)
game = Game.objects.get(id=game_id)
vote.game = game
vote.save() # You have to save the vote for it to be added to the db.
return HttpResponseRedirect(reverse('redirect_view'))

This had a painful solution, a simple typo. In my GameDetail's get_context_data I setup data for the template to use, one of those fields was the url to post to
ctx['vote_from_url'] = vote_form_url
In the template when I attempted to reference this I misspelled it
action="{{ vote_form_url }}" >
The frustrating thing for me is that this does not come up with an error, in the template if I put anything it defaults to the url of the parent view. I should have noticed that POST was going to the wrong URL, but I wonder if there is a setting or log that would have told me I was referencing something in the template that doesn't exist. Hope this recap helps someone that get stuck in a similar situation.

maybe a bit late and not exactly the same case but i had the same issue with FIELDSET :
PK(id) had to be included in the form.
Discovered this with adding this in my template:
{% for hidden_fields in formset %}
{{ hidden_fields.errors }}
{% endfor %}
Then, when "SUBMIT", this showed :
id
Ce champ est obligatoire.
id
Ce champ est obligatoire.
id
Ce champ est obligatoire...
(champ obligatoire <=> mandatory field)
After adding the hidden TD, data were well saved :
<tbody>
{% for form in formset %}
<tr>
<td class="hidden">{{ form.id }}</td>
<td>{{ form.ean }}</td>
<td>{{ form.used }}</td>
</tr>
{% endfor %}
</tbody>
Not sure it's the best way of doing but it worked for me

Related

Accessing profile info through user model Django

Hello I am a beginner with the django python framework. I need to display a user's image and bio on a file named user_posts.html in my blog app. I have it where I can access the user's image and bio by looping over the posts for the user. However, I need it so it only displays the bio and the image once. I have a separate profile.html in a users app. In that file, I can do just src="{{ user.profile.image.url }}" and {{ user.profile.bio }} to access the users information but that does not seem to work in my user_posts.html because of the structure of my project. I can't figure out how to tell the for loop to just go over once to access the users information.
users_post.html
{% extends "blog/base.html" %}
{% block content %}
<hr size="30">
<div class="row">
<div class="column left">
{% for post in posts %}
<img style= "float:left" src="{{ post.author.profile.image.url}}" width="125" height="125">
<h5 style="text-align: left;">{{ post.author.profile.bio }}</h5>
{% endfor %}
</div>
views.py
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
models.py
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
bio = models.TextField(default='enter bio text here')
def __str__(self):
return f'{self.user.username} Profile'
This is what the problem looks like
Any help is appreciated
Here's how you might do it using get_context_data().
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
def get_context_data(self, **kwargs):
"""
Add User Profile to the template context.
"""
context = super().get_context_data(**kwargs)
profile_user = get_object_or_404(User, username=self.kwargs.get('username'))
context['profile_user'] = profile_user
return context
You'd then use {{ profile_user.profile.bio }} in your template instead of posts for the user profile info.
This is probably better than getting the first object in posts and getting the user profile information from that object in the case the user has no posts yet (but has a bio).
Note, we're fetching the User object both in get_queryset and in get_context_data so this is not super efficient. There are ways around this but I'll leave it to future editors or you to optimize :)
Probably Not Recommended, But Answering Question
Since you initially were just trying to get the first element, here's how I'd do it.
# Method 1
{% with first_post=posts|first %}
{{ posts.user.profile.bio }}
{{ posts.user.profile.image.url }}
{% endwith %}
# Method 2
{{ posts.0.profile.bio }}
{{ posts.0.profile.image.url }}

How to Create A subscription button in Django

I'm new to web development and Python programming.
I want to create a button where users can subscribe to and unsubscribe from a sub-channel (much like a forum category) in a forum.
What I really want is for a user to be capable of subscribing and unsubscribing by clicking the button of the sub-channel.
views.py:
class SubChannelSubscriptionView(ListView):
template_name = 'subscription.html'
model = Topic
def get_queryset(self):
return SubChannelSubscription.objects.filter(user=self.request.user)
def get_context_data(self, **kwargs):
context = super(SubChannelSubscriptionView, self).get_context_data(**kwargs)
context['SubChannel'] = SubChannel.objects.all()
context['top'] = Topic.objects.filter(category__subchannel_subs__user=self.request.user)
return context
def subd(self, request):
subchannel = get_object_or_404(SubChannel, pk=self.kwargs['pk'])
is_subd = False
if subchannel.subd.filter(pk=request.user).exists():
subchannel.subd.remove(request.user)
is_subd = False
else:
subchannel.is_subd.add(request.user)
is_subd = True
return reverse('index')
models.py:
class SubChannel(models.Model):
title = models.CharField(max_length=150, unique=True, null=True)
description = models.CharField(max_length=150, null=True)
subd = models.ManyToManyField(User, related_name='subd' )
subscription.html:
<form action="{% url 'index' %}" method="post">
{% csrf_token %}
{% if is_subd %}
{% for sb in SubChannel %}
<button type="submit" value="" > subd{{ sb }} </button>
{% endfor %}
{% else %}
{% for sb in SubChannel %}
<button type="submit" value="" >not sub {{ sb }}</button>
{% endfor %}
{% endif %}
</form>
a simple way to toggle between subscription is to create a url for it and execute it by ajax.
this url, in its view, we get the user and channel, then we can find by querying that if the user is subscribed to channel if True then remove him else add that user to the channel.
I can't post any code for it because I didn't understand the logic. Here I noticing that SubChannel have ForaignKey channel and ManyToManyField of User.
I think there should be only two Models
Channel
User -> ManyToManyField Channel
By this one can easily manage user having multiple channels and all users subscribed to a channel. Adding a Third DBTable of UserChannel is done internally by Django ORM. (I understand that this may not be sutaible for your specific requirements.)

Displaying content from 2 models on a single Django template

I am working with my first Django project and I have printed my first template tag using a view onto my page template. I'd like to display some other database values on my page, but repeating the same process seems wrong.
Here are my two models:
class Drink(models.Model):
drink_name = models.CharField(max_length=100)
strength = models.IntegerField(default=0)
def __str__(self):
return self.drink_name
class status(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
health = models.IntegerField(default=100)
strength = models.IntegerField(default=50)
charisma = models.IntegerField(default=50)
And my view:
from django.shortcuts import render
from .models import Drink
from .models import status
# Create your views here.
def index(request):
served_drinks = Drink.objects.order_by('strength')
context = {'served_drinks': served_drinks}
return render(request, 'tavern/index.html', context)
And my template:
Hello {{ user.get_username }}, welcome to the tavern.
<br><br>
<div class="card-panel teal lighten-3">Grab a drink from the bar</div>
{% if served_drinks %}
<ul>
{% for drink in served_drinks %}
<li>{{ drink.drink_name }} with a strength of {{ drink.strength }}</li>
{% endfor %}
</ul>
{% else %}
<p>No drinks are available.</p>
{% endif %}
Which returns:
Hello admin, welcome to the tavern.
Grab a drink from the bar
Water with a strength of 0
Wine with a strength of 3
Ale with a strength of 5
Black Ale with a strength of 9
As you can see I've printed out my served_drinks object in the template, but I also want to print out data from the status model on this page too.
I've tried simply adding {{ status.health }} to the template but this does not work. Should I be adding extra information to the index view? If so, what if I want to show this information on many pages?
You should pass your status instance to the context of the view as well. For example, let's get a first instance of status model in database:
def index(request):
served_drinks = Drink.objects.order_by('strength')
status = status.objects.all().first()
context = {'served_drinks': served_drinks, 'status': status}
return render(request, 'tavern/index.html', context)
Now, you can use {{ status.health }} in your template.
Also, try to start your model names with capital letters. So, model Status is better than status. It's just good practices.
Since you're using the render shortcut, you can add whatever you want to the context dict:
from django.contrib.auth.decorators import login_required
#login_required
def index(request):
served_drinks = Drink.objects.order_by('strength')
context = {
'served_drinks': served_drinks,
'status': Status.objects.get(user=request.user),
}
return render(request, 'tavern/index.html', context)
Then you can use {{ status.health }} in the template. Note the login_required decorator is used so that only logged-in users can access the view, otherwise the Status.objects.get() would fail for anonymous users. I would also rename your model from status to Status, so that you do not confuse the Status model with status instances.
However, you don't actually need to add status to the context in this case. You can follow the one-to-one field backwards from user to status:
{{ user.status.health }}

Django template: access query_set and non-query_set results in same template

I have a Django app that contains info on schools and states. I want my template to display a list of schools per state and also the name of the state based on the state parameter in the URL. So if a user goes to example.com/vermont/ they will see a list of Vermont schools and a tag that says they're on the "Vermont" page. I can get the list of schools per state to work, but I can't figure out how to simply list the state name in the h1 tag.
Here is my models.py:
from django.db import models
class School(models.Model):
school_name = models.CharField(max_length=200)
location_state = models.CharField(max_length=20)
def __unicode__(self):
return self.school_name
Here is my views.py:
from django.views.generic import ListView
class StateListView(ListView):
model = School
template_name = 'state.html'
context_object_name = 'schools_by_state'
def get_queryset(self):
state_list = self.kwargs['location_state']
return School.objects.filter(location_state=state_list)
And here's my template for state.html:
{% extends 'base.html' %}
{% block content %}
<h1>{{school.location_state }}</h1> [THIS IS THE LINE THAT DOES NOT WORK]
{% for school in schools_by_state %}
<ul>
<li>{{ school.school_name }}</li>
</ul>
{% endfor %}
{% endblock content %}
What am I missing here?
The problem is that the school variable never enters the context. You are only setting the schools_by_state to the context.
To add some extra context you need to override the get_context_data method. This way you can add the location_state from the url parameter:
def get_context_data(self, **kwargs):
context = super(StateListView, self).get_context_data(**kwargs)
context.update({'state': self.kwargs['location_state']})
return context
Then you can use the {{ state }} instead of {{ school.location_state }} in your template.

Django ~ WSGIRequest with a form added via get_context_data method

I am getting this error when i visit my page:
Caught AttributeError while rendering: 'WSGIRequest' object has no attribute 'get'
The error kicks in on line "17" of my html, which is the line that outputs form.as_p
The html looks like this:
{% extends "base.htm" %}
{% block content %}
{% if story_list %}
{% for story in story_list %}
<div class="Story">
{{ story.title }}
</div>
{% endfor %}
{% else %}
<p>No stories are present - enter one below</p>
{% endif %}
<h3>Create a new story</h3>
<form action="/addStory" method="post">
{% csrf_token %}
{{ form.as_p }} ***THIS IS LINE 17***
<input type="submit" value="Submit"/>
</form>
{% endblock %}
The problem is i have a view that does two things, and from the django tutorials overrode the get_context_data method to add the second item to the django context. Because, um, that's what i'm meant to do, riiiiiiiiight?
#for showing of the stories!
class StoryShowView(ListView):
model = StoryForm
def get_queryset(self):
return getStoryItemsForUser(self.request)
def get_context_data(self, **kwargs):
context = super(StoryShowView,self).get_context_data(**kwargs)
context['form'] = createNewStoryForm(self.request)
return context
Where, well, the method createNewStoryForm just does this:
def createNewStoryForm(request):
return StoryForm(request)
and StoryForm is just this:
class StoryForm(ModelForm):
class Meta:
model = Story
ordering = ['create_date']
and the Story model is a normal model, that probably isn't part of the problem, but, hey, i am a cutting and a pasting, so here goes!
class Story(models.Model):
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
title = models.CharField(max_length=100)
is_closed = models.BooleanField()
is_random = models.BooleanField() # for uncategorised stories. Only one of these.
result = models.CharField(max_length=20) #how did the relo work out?
create_date = models.DateTimeField('date created')
def __unicode__(self):
return self.title
Any ideas what i am doing wrong?
UPDATE:
ah, it was the line::
return StoryForm(request)
I take it i can either pass in a "request.POST" or nothing, is that it?
Probably you're right and you were passing request instead of request.POST, reqest.GET or request.REQUEST to the constructor of your form. See the doc on how to use forms:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
Two problems that I can see. The easy one being that you can simply replace this line:
context['form'] = createNewStoryForm(self.request)
with
context['form'] = StoryForm(request.POST, request.FILES)
Finally shouldn't this:
class StoryShowView(ListView):
model = StoryForm
Be:
class StoryShowView(ListView):
model = Story

Categories