Django model doesn't relate itself to User through ForeignKey - django

my question is about modelforms, models and instances. After doing some troubleshooting I think my problem is that either the user field from UserFile doesn't associate itself to the auth.User or that the modelform doesn't pass the instance of auth.User. The error is at the dynamic pathing - file_destination - when I try self.user it can't find the user :/
# Model
class UserFile(models.Model):
user = models.ForeignKey('auth.User', related_name='user_file', primary_key=True, unique=True)
user_file = models.FileField(upload_to=file_destination, null=True)
def __unicode__(self):
return self.user_file.name
# View
def login_index(request):
template = 'loginIndex.html'
context = Context()
if request.user.is_authenticated():
if request.method == 'POST':
form = UserUpload(request.POST, request.FILES, instance=request.user)
context.update({'form': form})
if form.is_valid() and form.is_multipart():
instance = UserFile(user_file=request.FILES.get('user_file'))
instance.save()
else:
form = UserUpload()
context.update({'form': form})
return render(request, template, context)
else:
return render(request, template, context)
# Form
class UserUpload(ModelForm):
user_file = forms.FileField(required=False, widget=forms.ClearableFileInput, label='Upload')
class Meta:
model = UserFile
fields = ['user_file']
def clean_user_file(self):
check_user_file = self.cleaned_data.get('user_file')
if check_user_file:
if check_user_file.size > 5120000:
raise ValueError('File is too big for upload')
return check_user_file
# The problem arises when I submit the instance, which saves the file from the form to upload_to=file_destination
# In file_destination I get an error on self.user.username saying || DoesNotExist at "" UserFile has no user.
# My self.user is an None object.
def file_destination(self, filename):
filename = name_generator()
url = "%s/%s/%s" % (self.user.username, 'uploads' ,filename)
return url

You need to manually set the user field on your UserFile instance:
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instanve.save()
form.save_m2m() # add this if you add m2m relationships to `UserFile`
Also, it is a good idea to redirect after the form handling succeeds:
from django.shortcuts import redirect
# ...
return redirect("view-name")

Related

2 Forms on same model not saving as same user - Django

I'm creating a questionnaire / survey, and have two forms (Model Form) built on the same model. These forms are called on separate views, but when saved they appear as separate users in the database. I'm not sure how to get them so save as the same user, I am already using the ' post = form.save(commit=False), post.user = request.user, post.save()' method to save the forms.
EDIT: Added in an attempt to save to the same instance
Model:
class QuizTakers(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
industry_choices = (
(1, 'Service'),
(2, 'Hospitality'),
(3, 'Wholesale/Retail'),
(4, 'Manufacturing'),
(5, 'Agriculture')
)
industry = MultiSelectField(choices=industry_choices, max_length=1, max_choices=1)
company_name = models.CharField( max_length=100)
email = models.EmailField(blank=True)
score = models.FloatField(default=0)
completed = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.company_name
Forms:
# Form for getting company name
class QuizTakerForm(forms.ModelForm):
class Meta:
model = QuizTakers
fields = ['company_name']
# Form for getting company industry
class QTIndustryForm(forms.ModelForm):
class Meta:
model = QuizTakers
fields = ['industry']
Views:
# view for getting company name
def start(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = QuizTakerForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
request.session['company_name'] = form.cleaned_data['company_name']
post = form.save(commit=False)
post.user = request.user
post.save()
# redirect to a new URL:
return HttpResponseRedirect('industry/')
# if a GET (or any other method) we'll create a blank form
else:
form = QuizTakerForm()
return render(request, 'ImpactCheck/start.html', {'form': form})
# view for getting industry
class IndustryView(FormView):
template_name = 'ImpactCheck/industry.html'
form_class = QTIndustryForm
success_url = '1/'
def get(self, request):
company_name = request.session['company_name']
this_user=QuizTakers.objects.filter(company_name=company_name).order_by('-timestamp').first()
form=self.form_class(instance=this_user)
company_name = request.session['company_name']
return render(request, 'ImpactCheck/industry.html', {'form': form, 'company_name': company_name})
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
post = form.save(commit=False)
post.user = self.request.user
post.save()
return HttpResponseRedirect('/1')
Firstly, in your def start(request) function, you should consider adding the ID to request.session instead of the company name. Something along the lines of
def start(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = QuizTakerForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
form.instance.user=request.user
form.save()
request.session['obj_id'] = post.id
# redirect to a new URL:
return HttpResponseRedirect('industry/')
Now you can use that id to get both the name of your company, as well as the object.
In your IndustryView(FormView), if you're having trouble with the form instances, it's better to use UpdateView instead of the FormView (Be sure to import UpdateView first)
class IndustryView(UpdateView):
template_name = 'ImpactCheck/industry.html'
model = QuizTakers
fields = ['industry']
success_url = '/1'
def get_object(self):
return QuizTakers.objects.get(pk=self.request.session.get('obj_id'))
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['company_name'] = QuizTakers.objects.get(pk=self.request.session.get('obj_id'))
return ctx
We use the get_context_data method since you need the company_name in your template. The get_object method in this view, tells django which object is to be updated. By default, it grabs the pk from the url (as a url parameter). But since we store our id in the session, we need to explicitly define this function.
Also, since we switched to UpdateView, you no longer need the QTIndustryForm either.

"Select a valid choice. <choice> is not one of the available choices" error when submitting ManyToMany ModelForm

I want to limit the choices of a ManyToManyField to those matching a ForeignKey. The form displays properly, but upon saving results in an error Select a valid choice. <choice> is not one of the available choices.
Before I was trying to limit the queryset by passing a parameter in the view to the form, and then using that parameter to filter the queryset.
Models:
class VenueEventTimeslot(models.Model):
venue = models.ForeignKey(Venue)
name = models.CharField(max_length=255)
class VenueEvent(models.Model):
venue = models.ForeignKey(Venue)
event_timeslots = models.ManyToManyField(VenueEventTimeslot)
class VenueEventForm(ModelForm):
event_timeslots = ModelMultipleChoiceField(queryset=None, widget=CheckboxSelectMultiple())
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj',None)
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue_obj)
class Meta:
model = VenueEvent
fields = ['event_timeslots']
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm(venue_obj = venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)
However, if I pass the queryset from the view, it works perfectly.
Models:
class VenueEventTimeslot(models.Model):
# same as above
class VenueEvent(models.Model):
# same as above
class VenueEventForm(ModelForm):
class Meta:
model = VenueEvent
fields = ['date','client_name','event_timeslots']
widgets = {
'date': SelectDateWidget(),
'event_timeslots': CheckboxSelectMultiple(),
}
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm()
form.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue)
context = {'venue':venue, 'form':form}
return render(request, ..., context)
Would anyone be able to shed some light on this?
I just solved a problem similar to this yesterday which is right here, How To Exclude A Value In A ModelMultipleChoiceField?, but I think the issue with your init function is the way it is formatted. Instead of venue=venue_obj, you need to change it to pk=venue_obj because it appear you are getting the pk of venue in the view instead of the venue attribute of VenueEvent , and I reformatted your form a bit to make it look cleaner.
forms.py
class VenueEventForm(ModelForm):
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj')
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(), queryset=VenueEventTimeslot.objects.filter(pk=venue_obj))
class Meta:
model = VenueEvent
fields = ['event_timeslots']
views.py
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST, venue_obj=venue)
if form.is_valid():
# form stuff
else:
print VenueEventForm.errors
else:
form = VenueEventForm(venue_obj=venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)

django forms - how to filter number of available options in a form

I'm trying to limit number of "categories" that user have available when entering new "feed" only to categories that he owns and he created. The way it works now is that user can add "feed" to other users' "categories" as this is what the form displays. How can I fix it ?
thanks!
-M
models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User)
class Feed(models.Model):
url = models.URLField()
name = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
description = models.TextField(blank=True)
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
forms.py
class FeedForm(forms.ModelForm):
class Meta:
model = Feed
exclude = ['user']
views.py
def addfeed(request, user):
user = request.user
page_title = "Add feed"
instance = Category.objects.filter(user=request.user)
if request.method == 'POST':
form = FeedForm(request.POST, instance=instance)
if form.is_valid():
feed = form.save(commit=False)
feed.user = request.user
feed.save()
return HttpResponseRedirect("/user/" + user.username + "/manage")
else:
form = FeedForm()
return render(request, "form_manage.html", {
'page_title': page_title,
'form': form,
})
Set the queryset attribute of the field somewhere. Because it depends on your user, it's something you have to set during or after instantiating the form. For instance, here's how to do it in the view:
def addfeed(request, user):
user = request.user # why does this view take user as an arg and then reassign?
page_title = "Add feed"
categories = Category.objects.filter(user=request.user)
if request.method == 'POST':
form = FeedForm(request.POST)
form.fields['category'].queryset = categories
if form.is_valid():
feed = form.save(commit=False)
feed.user = request.user
feed.save()
return HttpResponseRedirect("/user/" + user.username + "/manage")
else:
form = FeedForm()
form.fields['category'].queryset = categories
return render(request, "form_manage.html", {
'page_title': page_title,
'form': form,})
I removed the instance argument to your POST case's form construction because that's meant for passing in an existing Feed instance, not a categories queryset.
You could also do this in the form's __init__ if you pass in the correct categories queryset.
I use javascript to do this. For example, you could pass a list of the relevant categories as extra context in your view then use javascript in your template to empty the pre-populated option field in the form and replace it with your extra context.

Django: Save id on a OneToOneField

models:
class UserDataUpdate(models.Model):
code = models.CharField(max_length=8)
address = models.CharField(max_length=50)
class UserSurvey(models.Model):
about_treatment = models.CharField(max_length=2)
user_data_update = OneToOneField(UserDataUpdate)
views:
#login_required
def generate_survey(request):
user_data_update = UserDataUpdate.objects.get(code=request.user.username)
if request.method == 'POST':
form = SurveyForm(request.POST)
if form.is_valid():
form.save()
return redirect('/success')
else:
form = SurveyForm(request.GET)
return render_to_response(
'survey.html',
{'form': form },
context_instance = RequestContext(request))
form:
class SurveyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SurveyForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget = RadioSelect(choices=SURVEY_CHOICES)
class Meta:
model = Survey
exclude = ['user_data_update']
I just need a way to set the UserDataUpdate id (that already has been created) on a UserSurvey.
I'm getting this message on generate_survey request.POST:
user_data_update_app_usersurvey.user_data_update_id may not be NULL
It should be clear to you that you get the user_data_update value but then don't do anything with it. I guess you want to set it on the object that's created by the form:
if form.is_valid():
instance = form.save(commit=False)
instance.user_data_update = user_data_update
instance.save()
(I don't understand what all that stuff in the form's __init__ method is supposed to do. You only have one field in your form, anyway.)

How to override FileField and automatically delete previous file in Django?

im django beginner (django 1.2.5)
I have that model:
class Document(models.Model):
file = models.FileField(upload_to='documents/%Y/%m/%d', null=True, blank=True)
title = models.CharField(max_length=30)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User)
#other fields
#other fields
and model form to this:
class DocumentForm(ModelForm):
file = forms.FileField(required=True, error_messages={'required' : 'required!','empty': "empty!"})
title = forms.CharField(widget = forms.TextInput(attrs={'size': 93,}), error_messages={'required': 'required!'})
#other fields
#other fields
class Meta:
model = Document
exclude = ('author',)
def save(self, author, commit=True):
document=ModelForm.save(self,commit=False)
document.author = author
if commit:
document.save()
return document
I uploading new documents in using DocumentForm above and it works pretty but when i trying edit some document i cannot put new file in place previous. I may change every field except FileField.
def document_edit(request, document_id):
doc = get_object_or_404(Document, id=document_id)
form = DocumentForm(instance=doc)
if doc.author == request.user:
if request.method == "POST":
form = DocumentForm(request.POST, request.FILES, instance=doc)
if form.is_valid():
if request.POST.get('cancel'):
return HttpResponseRedirect('/')
elif request.POST.get('delete'):
document = Document.objects.get(id=document_id)
document.file.delete()
document.delete()
return HttpResponseRedirect('/')
else:
form.save(author=request.user)
return HttpResponseRedirect('/')
else:
# return again form with errors
else:
# return form with doc instance
else:
# return "you can't edit this doc!"
I research django documentation and i only know i should write some custom save method in some class but i completely have no idea how can i do this. It should be save() method in Document() or in DocumentForm()?
Generally i want this: When i put path to new file in form i want override this new file in his place and automatically delete previous file.
Can you help me? Thanks in advance!
You are on the right track, you just want to use the instance keyword, so your form reflects the object being edited. Simplified version:
def edit_upload(request, document_id):
doc = get_object_or_404(Document, id=document_id)
if request.method == 'POST': # If the form has been submitted...
form = DocumentForm(request.POST, request.FILES, instance=doc)
if form.is_valid(): # All validation rules pass
if doc.file: # If document has file already...
doc.file.delete() # delete it
form.save() # Saves object, uses new uploaded file
return redirect('/thanks/') # Redirect after success
else:
form = DocumentForm(instance=doc) # Show form to edit
return render(request, 'edit.html', {
'form': form,
})