Django - Can't get User as ForeignKey to work - python

I am building a Django site to share code for Python games, but I'm running into trouble associating each game with a specific user. I'm trying to do this by using the user as the ForeignKey, which I think is the right approach, but I can't get the user to actually get inserted into the database without using some weird hack (like passing the user as a string into a hidden user field, which is totally insecure and causes other issues.)
My Game class looks like this:
class Game(models.Model):
user = models.ForeignKey(User, blank=True)
title = models.CharField(max_length=256)
image = models.ImageField(upload_to='games')
description = models.CharField(max_length=256)
requirements = models.CharField(max_length=256)
code = models.CharField(max_length=256000)
def __unicode__(self):
return self.title
The form for adding a game:
class GameForm(forms.ModelForm):
image = forms.ImageField(required=False)
code = forms.CharField(widget=PagedownWidget(show_preview=True))
class Meta:
model = Game
fields = ('user','title','image','description','requirements','code')
User.game = property(lambda u: Game.objects.get_or_create(user=u)[0])
I create a custom version of this form in my addgame.html template:
<form action="/userprofile/addGame/" method="post">{% csrf_token %}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}
<br>
{{ field }}
<br><br>
</div>
{% endfor %}
<input type="submit" value="Save">
</form>
My View looks like this:
#login_required
def add_game(request):
user = request.user
error_message = None
if request.method == 'POST':
form = GameForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
form.uploader = request.user
form.save()
return HttpResponseRedirect('/userprofile')
else:
error_message = "Invalid Input"
else:
form = GameForm()
args = {}
args.update(csrf(request))
args['user'] = user
args['form'] = form
args['error_message'] = error_message
return render_to_response('addgame.html', args)
However, I get the following error:
Cannot assign None: "Game.user" does not allow null values.
I realize that a User instance has not been passed into the form, thus I am getting a 'shouldn't be null' error, but I have no idea how to properly pass in the User instance to the form. I've looked at several questions on this exact issue, such as this, but I still haven't been able to get User successfully passed in to the form.

Problem 1:
form.uploader = request.user
Should be:
form.user = request.user
I dont see uploader attribute in your Game model
Problem 2:
In the GameForm Meta class, the following line should not include a user field:
fields = ('title','image','description','requirements','code')

Related

First argument to get_object_or_404() must be a Model. How can I get an user's id to the User model?

I'm trying to make a favorite functionality where an user can add other users as their favorites.
In the View where the profile of an user is shown I have a button that adds an user or removes it if it was already added.
The problem is that I can't pass to the views the user that will be added as a favorite.
models.py
class User(AbstractUser):
is_type1 = models.BooleanField(default=False)
...
class Type1(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
favorite = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, related_name='favorite')
views.py
def FavoriteView(request, pk):
current_user = request.user
Type1.user = current_user.id
buser = Type1.user
Type1.favorite = get_object_or_404(User, id=request.POST.get('username')) # The of the error where I try to add the user being added as a favorite
fuser = Type1.favorite
if Type1.favorite.filter(id=request.user.id).exists():
Type1.favorite.remove(request.user)
else:
Type1.favorite.add(request.user)
return HttpResponseRedirect(reverse('profile-details', kwargs={'username': Type1.favorite}))
class UserView(DetailView):
model = User
...
template_name = 'users/profile-details.html'
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
favorite_connected = get_object_or_404(Type1.favorite, id=self.kwargs['username']) # The of the error where I try to add the user being added as a favorite
favorite = False
if favorite_connected.favorite.filter(id=self.request.user.id).exists():
liked = True
data['user_is_favorite'] = favorite
return data
profile-details.html
...
{% if user.is_authenticated %}
<form action="{% url 'favorite' object.id %}" method="POST">
{% csrf_token %}
{% if user_is_favorite %}
<button type="submit" name="favorite" value="{{object.id}}">Not favorite</button>
{% else %}
<button type="submit" name="favorite" value="{{object.id}}">Favorite</button>
{% endif %}
</form>
{% else %}
Log in to add user to favorites.<br>
{% endif %}
urls.py
path('profile/<str:username>/', UserView.as_view(), name='profile-details'),
path('favorite/<str:username>/', FavoriteView, name="favorite"),
One immediate problem I see is that your URL path wants a string for the username, but your URL for the form gives it the ID of the user, so that'll be an int.
In terms of your error, you're trying to pass a username, but I don't think that'll be in the POST data. However if it was, you should be able to do;
get_object_or_404(User, username=request.POST.get('username'))
However, based on my initial comment, you should probably just get the user by ID like you are doing, but use the PK of the user which is comes with your view;
get_object_or_404(User, id=pk)
You may also come across more errors, because you're assigning an object, if it exists to Type1.favorite and then attempting to do Type1.favorite.filter( which will fail. You can only .filter() on a queryset, not a model instance.

Django adding to database with foreign key while still showing the informations from the other model

I want to add elements to my database (for model Student) while having stuff from another model (School) be displayed alongside the form for the Student.\
This is the models.py
class School(models.Model):
name = models.CharField(max_length=256)
principal = models.CharField(max_length=256)
location = models.CharField(max_length=256)
def get_absolute_url(self):
return reverse('basiccbv:detail', kwargs={'pk':self.pk})
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=256)
age = models.PositiveIntegerField(validators= [validators.MinValueValidator(1),validators.MaxValueValidator(20)],default=1)
school = models.ForeignKey(School, related_name='students')
def __str__(self):
return self.name
In my views.py I have this:
class SchoolDetailedView(DetailView):
context_object_name = 'school_detail'
model = models.School
template_name = 'basiccbv/school_detail.html'
# What i want is when I visit the link in the description I want to
# to see the school stuff and the form to add the student in this new
# view
class StudentCreateView(CreateView):
model = models.School
# I tried using the Student but that I don't know how to display the
# school information, I tried with related_name = 'students' but it
# didn't work(I don't know if that can be done the way that intended it
# or I just don't have the knowledge )
fields = ['name', 'age']
# If I use School I could get the name of the school in the title and
# its primary key, but than I don't know how to display the form and
# vise versa
template_name = 'basiccbv/student_update.html'
This is the .html file that gets me to the page where I need the form.
The link is the one calling 'basiccbv:studentupdate'
The related_name students was used here but I still can't figure out if it can
be done for adding stuff the way I want
<h1>Welcome to the school details page</h1>
<h2>School details:</h2>
<p>Name: {{ school_detail.name }}</p>
<p>Principal: {{ school_detail.principal }}</p>
<p>Location: {{ school_detail.location }}</p>
<h3>Students:</h3>
{% for student in school_detail.students.all %}
<p>{{ student.name }} who is {{ student.age }} years old.</p>
{% endfor %}
<div class="container">
<p><a href="{% url 'basiccbv:studentupdate' pk=school_detail.pk %}">Add a
student</a></p>
And here is the .html file with the form
## I changed this part bellow but nothing worked
<h1>Student coming to {{ student.school.name }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-primary" value="Add student">
</form>
I'm really stuck and can't find any information about this but if you can help me or give any advice thank you.
The way I used to add students was with admin and for schools I used admin until I created the view for creating Schools which worked with no problems(probably because there were no foreign keys).
I think you can take this approach
Forms:
# We need to define a new form first
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = ['name', 'age']
Views:
# we are using form view for using the form mentioned above
class StudentCreateView(FormView):
form_class = StudentForm
success_url = "/"
def get(self, request, school_id, **kwargs):
context = self.get_context_data(**kwargs) # getting context, ie: the form
context[school] = School.objects.get(pk=school_id) # updating the context with school object using the PK provided with the url
return self.render_to_response(context)
def post(self, request, school_id, **kwargs):
# overriding default implementation
form = self.get_form()
if form.is_valid():
return self.form_valid(form, school_id) # passing the pk value to form valid function to override
else:
return self.form_invalid(form)
def form_valid(self, form, school_id):
# overriding default implementation
self.object = form.save(commit=False)
self.object.school = School.objects.get(id=school_id) # saving the school information to the object
self.object.save()
return super(StudentCreateView, self).form_valid(form)
Template
# template
<h1>Student coming to {{ school.name }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-primary" value="Add student">
</form>
Urls
path('school/student-update/<int:school_id>/', StudentCreateView.as_view(), name='studentupdate'),

Django form missing fields

I'm trying to get a simple form working. Oddly, other forms I wrote in this app are working fine, but this one wont show the fields. Can anyone tell me what I'm missing? Here are the files
views.py:
def newnote(request, record_id):
if request.method == 'POST':
form = NoteForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/tracker/all/')
else:
form = NoteForm()
return render(request, 'tracker/noteform.html', {'form': form})
models.py
class Note(models.Model):
record = models.ForeignKey(Record, on_delete=models.CASCADE)
note_text = models.CharField('Notes', max_length=2000)
note_date = models.DateField('Date Entered')
forms.py
class NoteForm(forms.Form):
class Meta:
model = Note
fields = ['note_text',
'note_date'
]
template (noteform.html)
<form action="/tracker/newnote/" method="post">
<div id="fields">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</div>
</form>
One other note, I have commented out the div id called "fields", to rule out CSS as the issue.
Your form is based on form.Form, which doesn't know anything about models, doesn't expect a Meta class, and expects all its fields to be declared manually - since you have not declared any fields, nothing will show on the template.
It should inherit forms.ModelForm instead.

Attaching a current User object to Django form

I am working on an app that has a section with with a file upload form for .txt fiels. I would like for the current user that is uploading the file to be added along with the file and the file name. Currently, I can do this successfully in the admin section but I just cant get it to save via the form itself. Any Ideas?
Here are the models:
class UploadedTextFile(models.Model):
file = models.FileField(upload_to="textfiles")
filename = models.CharField(max_length = 50)
username = models.ForeignKey(User, blank=True, null=True)
class UploadedTextFileForm(ModelForm):
class Meta:
model = UploadedTextFile
fields = ['file', 'filename']
Here is my view:
def inputtest(request):
#response for file being submited
if request.method == "POST":
form = UploadedTextFileForm(request.POST)
if form.is_valid():
new_form = form.save(commit=False)
new_form.username = request.user
new_form.save()
return render(request, 'about.html')
inputtest = UploadedTextFileForm()
return render(request, 'failed.html', {'inputtest': inputtest})
else:
inputtest = UploadedTextFileForm()
return render(request, 'inputtest.html', {'inputtest': inputtest})
Here is my html:
{% extends 'base.html' %}
{% block content %}
<form method="post">{% csrf_token %}
{{ inputtest.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock content %}
Doing it in the view (as you've shown) is the right way to do this. Most likely you're having problems because you've left username as a field on the form, and because the FK model field doesn't have blank=True set the form requires the field to be provided. You should explicitly declare just the subset fields that you want to accept user input for in the form's Meta class.
class UploadedTextFileForm(ModelForm):
class Meta:
model = UploadedTextFile
fields = ['file', 'filename']
I am not sure why you're rendering a different template when the form is not valid, but no matter what you're not providing the form object in the context. This means that you'll never see any errors the form detects, which is probably what's happening with this code - you're not seeing the error that username is not provided.

Extending User and UserCreationForm

I need your help.
I extending class User and add same field, than extending UserCreationForm, but form is not valid.
Code crash in if form.is_valid().
Please help, why my form is not correctly?
models.py
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='profile')
nick_name = models.CharField(max_length=15)
My register form
forms.py
class MyRegisterForm(UserCreationForm):
print "OK!"
nick_name = forms.CharField(max_length=30, required=True, widget=forms.TextInput)
print "Ook"
class Meta:
model = UserProfile
def save(self, commit=True):
if not commit:
raise NotImplementedError("Can't create User and UserProfile without database save")
print "Saving..."
user = super(MyRegisterForm, self).save(commit=False)
user.nick_name = self.cleaned_data["nick_name"]
user_profile = UserProfile(user=user, nick_name=self.cleaned_data['nick_name'])
user_profile.save()
print "Saving complete"
return user, user_profile
Register function
views.py
def reg(request):
if request.method =='POST':
form = MyRegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
print username
password1 = form.cleaned_data['password1']
print password1
password2 = form.cleaned_data['password2']
print password2
nick_name = form.cleaned_data['nick_name']
print nick_name
form.clean_username()
if password1 == password2:
new_user = form.save()
return render_to_response('registration/registration_complete.html')
else:
print "Password error"
return render_to_response('registration/registration_fail.html')
else:
print "FORM error" #ТУТ ВАЛИТСЯ :(
return render_to_response('registration/registration_fail.html')
else:
form = UserCreationForm() # An unbound form
return render_to_response('registration/registration_new_user.html', {
'form': form,
},context_instance=RequestContext(request))
In setting
settings.py
AUTH_PROFILE_MODULE = 'registration.UserProfile'
Registration template
registration_new_user.html
{% extends "base.html" %}
{% block content %}
<h1>Registration</h1>
<form action="registration" method="post">
{% if form.error_dict %}
<p class="error">Please fix the error.</p>
{% endif %}
{% if form.username.errors %}
{{ form.username.html_error_list }}
{% endif %}
<label for="id_username">Login:</label><br> {{ form.username }}<br>
{% if form.password1.errors %}
{{ form.password1.html_error_list }}
{% endif %}
<label for="id_password1">pass:</label><br> {{ form.password1 }}<br>
{% if form.password2.errors %}
{{ form.password2.html_error_list }}
{% endif %}
<label for="id_password2">pass(again):</label><br> {{ form.password2 }}<br>
{% if form.nick_name.errors %}
{{ form.nick_name.html_error_list }}
{% endif %}
<label for="id_nick_name">nick:</label><br> {{ form.nick_name }}<br>
<br>
<input type="submit" value="Reg" />
</form>
{% endblock %}
Well, you have several issues in your code. For instance, you override UserCreationForm with MyRegistrationForm and indeed you instantiate the latter when the request is a POST, but when is not, you pass the template a normal UserCreationForm.
You do have a user in your UserCreationForm because this is a ModelForm whose model is UserProfile and there you have defined a user field. So it makes perfect sense that the forms complaint about this when you create it with the POST.
I don't see a very clear solution here because your code is somewhat tricky but first of all, use the same form with both GET and POST request type so this line in your views
form = UserCreationForm() # An unbound form
Would change for this one:
form = MyRegistrationForm() # An unbound form
In the template it won't appear the field user because you don't include them but it is in the form. As you are creating a new user, that field should be set to non-required because no user will be associated with the UserProfile because you are creating the user. You can set it to non-required adding the parameter blank=True to the model:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='profile', blank=True)
nick_name = models.CharField(max_length=15)
UPDATE:
This is the code for your base class UserCreationForm save method:
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
As you can see, this code assumes that the user has a set_password attribute, in order to fix this, you have to add a def set_password(self, raw_password) method to your UserProfile class. This error happens because the form base class is designed to be used with normal Django User class, any other error you may encounter like this you will probably solve it by adding the fields required to your UserProfile. This one solves like this:
class UserProfile:
...
def set_password(self, raw_password):
# whatever logic you need to set the password for your user or maybe
self.user.set_password(raw_password)
...
I hope this bring some light to the problem. Good luck!

Categories

Resources