I have come to an impasse when using a ModelForm.
I'm extending the User model that comes with Django, and I'm also using a ModelForm so the user can edit it.
Following the same example in the documentation, I would have this code.
models.py
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# In this case, department is optional, so I have set 'blank' and 'null' to True.
department = models.CharField(max_length=100, blank=True, null=True)
forms.py
class DepartmentForm(ModelForm):
class Meta:
model = Employee
fields = ['department',]
The problem comes at the view. I found that I need to pass an instance of the model to the form so the save() function works without having to customize it, but of course, user.employee has not been created yet, therefore it throws an error.
views.py
def DepartmentView(request):
# Here is the issue.
department = request.user.employee
if request.method == 'POST':
# I need to pass the instance here.
form = DepartmentForm(request.POST, instance=department)
if form.is_valid():
form.save()
else:
# And also here so it autocompletes the form.
form = DepartmentForm(instance=department)
return render(request, 'employee.html', {'form': form})
It works if I manually add a value to user.employee.department through the shell and then reload the page, otherwise the error is as follow.
RelatedObjectDoesNotExist at [something]
User has no employee.
Or something like that... I'm sorry, I didn't try the code above so the error could be a little different, but the concept is exactly the same.
I'm also sorry if this has been asked before. I did a Google search and couldn't find an answer to this issue.
You could use get_or_create to fetch the employee from the db, or create it if it doesn't exist.
department, created = Employee.objects.get_or_create(user=request_or_user, department='')
if request.method == 'POST':
form = DepartmentForm(request.POST, instance=department)
...
Another option is to use a signal, so that the related model is created when the user is created. Then you can assume that the employee already exists, and you can use request.user.employee instead of get_or_create.
Related
I'm using Django 1.11.2 to develop a website. I use ModelForms to edit my model instances on my website. Every field of the form gets the fitting value of the instance I want to edit via 'initial' in my view. It works fine for all fields except ManyToManyFields.
The relevant code looks like this:
models.py:
class model1(models.Model):
name = models.CharField(max_length=45, blank=False, null=False)
class model2(models.Model):
name = models.CharField(max_length=45, blank=False, null=False)
relation = models.ManyToManyField(model1)
the ModelForm in forms.py:
class model2_form(forms.ModelForm):
class Meta:
model = model2
fields = '__all__'
and the view I use to edit model2 intances:
def model2_edit(request, objectid):
link = 'Model2'
model2_inst = model2.objects.get(id=objectid)
form = model2_form(initial={'name': model2_inst.name,
'relation': ???})
if request.method == 'POST':
f = model2_form(request.POST, instance=model2_inst)
if f.is_valid():
f.save()
return HttpResponseRedirect('/model2')
return render(request, "edit_db.html",
{"form": form, "link":link})
Everytime I edit an instance of model2 via the ModelForm, the 'relations' of the instance that already exist aren't preselected ('initial' isn't working). If I save the form like this without selecting the relations again, they get deleted and that instance of model2 has no relations anymore.
At the place of the '???' in my code I tried many ways to get those relations already selected in the form, but I couldn't find a working way.
I hope I managed to describe my problem, thanks in advance for any help or ideas.
form = model2_form(initial={'name': model2_inst.name,
'relation': [i.id for i in model2_inst.relation.all()]})
You should provide the instance for GET and POST requests. This way, you do not need to provide initial data - Django will get the values from the instance automatically.
model2_inst = model2.objects.get(id=objectid)
form = model2_form(instance=model2_inst)
I'm having some trouble with a Django project I'm working on. I now have two applications, which require a fair bit of overlap. I've really only started the second project (called workflow) and I'm trying to make my first form for that application. My first application is called po. In the workflow application I have a class called WorkflowObject, which (for now) has only a single attribute--a foreign key to a PurchaseOrder, which is defined in po/models.py. I have imported that class with from po.models import PurchaseOrder.
What I'm trying to do is have a page where a user creates a new PurchaseOrder. This works fine (it's the same form that I used in my PurchaseOrder application), and then uses that instance of the class to create a WorkflowObject. The problem now, is that I get the error: ValueError: Cannot create form field for 'purchase' yet, because its related model 'PurchaseOrder' has not been loaded yet. I'm really not sure where to start with this. It was working ok (allowing me to create a new PurchaseOrder and forward to a url with its primary key in the url) until I added the view that should allow me to create a new WorkflowObject. I'll put that specific view here:
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django_tables2 import RequestConfig
from po.models import PurchaseOrderForm, PurchaseOrder
from workflow.models import POObject, WorkflowForm
def new2(request, number):
po=PurcchaseOrder.objects.get(pk=number)
if request.method == 'POST':
form = WorkflowForm(request.POST)
if form.is_valid():
new_flow = form.save()
return HttpResponse('Good')
else:
return render(request, 'new-workflow.html', {'form': form, 'purchase': po})
else:
form = WorkflowForm()
return render(request, 'new-workflow.html', {'form': form, 'purchase': po})
The lines of code that seem to be causing the error (or at least, one of the lines that is shown in the traceback) is:
class WorkflowForm(ModelForm):
purchase = forms.ModelChoiceField(queryset = PurchaseOrder.objects.all())
EDIT:
I seem to have made a very noob mistake, and included parentheses in my definition of WorkflowObject, that is, I had said purchase=models.ForeignKey('PurchaseOrder'), instead of purchase=models.ForeignKey(PurchaseOrder)
I had a similar problem and was able to resolve this by declaring all my modelForm classes below all my class models in my models.py file. This way the model classes were loaded before the modelForm classes.
Firstly, you can try reduce code to:
def new2(request, number):
po=PurcchaseOrder.objects.get(pk=number)
form = WorkflowForm(request.POST or None)
if form.is_valid():
new_flow = form.save()
return HttpResponse('Good')
else:
return render(request, 'new-workflow.html', {'form': form, 'purchase': po})
Secondly, I not understood why you at other case wrote forms.ModelChoiceField(...) and another case ModelForm instance forms.ModelForm ?
Seems, that there are nothing special in your WorkflowForm, so you can define it as follows:
class WorkflowForm(ModelForm):
class Meta:
model = WorkflowObject
Field for relation will be created automatically.
Documentation: Creating forms from models
Just ran into this problem. I had a string value in the to= value of a ForeignKey (intentionally). The error was thrown because I changed my app's name from messages to messaging (because messages conflicted with django.contrib.messages), but forgot to change the model's ForeignKey string value.
For example:
# messaging/models.py
class Thread(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
class Message(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
thread = models.ForeignKey('messages.Thread', on_delete=models.CASCADE) # <- here!
The error:
ValueError: Cannot create form field for 'thread' yet, because its related model 'messages.Thread' has not been loaded yet
The solution was simply to change:
models.ForeignKey('messages.Thread', ...
to:
models.ForeignKey('messaging.Thread', ...
I saw another answer here and other places on the web that recommend using user.get_profile when extending the built-in django user. I didn't do that in the below example. The functionality seems to be working fine, but is there a downside for not using user.get_profile()?
model
class UserProfile(models.Model):
user = models.ForeignKey(User, primary_key=True)
quote = models.CharField('Favorite quote', max_length = 200, null=True, blank=True)
website = models.URLField('Personal website/blog', null=True, blank=True)
class UserProfileForm(ModelForm):
class Meta:
model = UserProfile
fields = ('quote', 'website')
view
#login_required
def user_profile(request):
user = User.objects.get(pk=request.user.id)
if request.method == 'POST':
upform = UserProfileForm(request.POST)
if upform.is_valid():
up = upform.save(commit=False)
up.user = request.user
up.save()
return HttpResponseRedirect('/accounts/profile')
else:
upform = UserProfileForm()
return render_to_response('reserve/templates/edit_profile.html', locals(), context_instance=RequestContext(request))
The code works as you've written it, but because you don't pass an instance to your model it's a bit unusual, so it might take another Django developer a bit longer to work out what's going on.
The view you link to instantiates the model form with an instance, so that the existing profile values are displayed in the form. In your case, you'll get empty fields.
upform = UserProfileForm(instance=user.get_profile())
Because you don't provide an instance, saving would try to create a new user_profile, which we wouldn't want. That won't happen in your case, because you've made user the primary key, but that's a little unusual as well.
The main advantage of writing user.get_profile() is that you don't need to know which model is used for the user profile. If you are happy to hardcode UserProfile model in your code, you could put instance=UserProfile.objects.get(user=user) instead.
Model class
class Fuzz_Engine(models.Model):
id = PositiveTinyIntField(primary_key = True)
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
View Class
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
# input validation for add phone model form
if form.is_valid():
fuzzEngineToAdd = Fuzz_Engine (engine_name = request.POST['engine_name'], version = request.POST['version'])
fuzzEngineToAdd.save(force_insert=True)
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': fuzzEngineToAdd.id,'fe': fuzzEngineToAdd,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
I have looked into a few similar questions on this issue, tried to print out the errors but doesn't seem to appear.
Django Formsets - form.is_valid() is False preventing formset validation
form.is_valid() always returning false
I have a feeling that the cause of the error lies in the structure of my model form class.
The .is_valid is false as I have placed a code in that if statement and it doesn't run, however if I have an else statement(which is not here) for if it is not valid, it will appear.
Can anyone provide another way of debugging this kind of error?
Couple of issues, it's hard to debug the code if the code you paste in isn't formatted well. The indentations were a mess so I'm not sure if that's causing a problem.
It seems like you are manually assigning a foreign key for your model. I would suggest just letting django handle the id for the model:
class Fuzz_Engine(models.Model):
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
Your form looks fine:
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
Some problems I see in your views include:
you shouldn't use request.POST['field_names'] directly. you should be getting the cleaned_data from your form.
you can save the form directly because it is a ModelForm. if you need the instance that you just created, that is what is returned from the save method, you can set a variable and use that as shown below.
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
if form.is_valid():
instance = form.save()
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': instance.id,'fe': instance,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
With your original view, it looks like you are trying to save a Fuzz_Engine instance with no id.
DTing has some great points, but I suspect your actual problem is related to your explicit definition of the id field on your model. Unless you have a really good reason, you should never do this - Django defines an autoincrement field automatically, and there is rarely any point overriding this, unless you are using a legacy db that can't be changed.
In your case, you have defined it as a tinyint without autoincrement. That means that field is going to be required on any form, as it needs to be specified manually every time you create a new instance. You haven't shown the template you're using to display the form so it's impossible to be sure, but I imagine you're not showing this field at all.
If you really really want to carry on doing it this way, you will need to specify exclude = ('id',) on the form Meta. Then in your is_valid clause, taking on board DTing's recommendations, you'll need to do this:
if form.is_valid():
instance = form.save(commit=False)
instance.id = some_function_for_calculating_id()
instance.save()
But as I say, you shouldn't be doing that at all.
I have a simple model that is defined as:
class Article(models.Model):
slug = models.SlugField(max_length=50, unique=True)
title = models.CharField(max_length=100, unique=False)
and the form:
class ArticleForm(ModelForm):
class Meta:
model = Article
The validation here fails when I try to update an existing row:
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid(): # POOF
form.save()
Creating a new entry is fine, however, when I try to update any of these fields, the validation no longer passes.
The "errors" property had nothing, but I dropped into the debugger and deep within the Django guts I saw this:
slug: "Article with this None already exists"
So it looks like is_valid() fails on a unique value check, but all I want to do is update the row.
I can't just do:
form.save(force_update=True)
... because the form will fail on validation.
This looks like something very simple, but I just can't figure it out.
I am running Django 1.0.2
What croaks is BaseModelForm.validate_unique() which is called on form initialization.
I don't think you are actually updating an existing article, but instead creating a new one, presumably with more or less the same content, especially the slug, and thus you will get an error. It is a bit strange that you don't get better error reporting, but also I do not know what the rest of your view looks like.
What if you where to try something along these lines (I have included a bit more of a possible view function, change it to fit your needs); I haven't actually tested my code, so I am sure I've made at least one mistake, but you should at least get the general idea:
def article_update(request, id):
article = get_objects_or_404(Article, pk=id)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return HttpResponseRedirect(to-some-suitable-url)
else:
form = ArticleForm(instance=article)
return render_to_response('article_update.html', { 'form': form })
The thing is, as taurean noted, you should instantiate your model form with the object you wish to update, otherwise you will get a new one.
I was also searching for a way to update an existing record, even tried form.save(force_update=True) but received errors??
Finally by trial & error managed to update existing record. Below codes tested working. Hope this helps...
models.py from djangobook
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField(blank=True, verbose_name='e-mail')
objects = models.Manager()
sel_objects=AuthorManager()
def __unicode__(self):
return self.first_name+' '+ self.last_name
class AuthorForm(ModelForm):
class Meta:
model = Author
# views.py
# add new record
def authorcontact(request):
if request.method == 'POST':
form = AuthorForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
form = AuthorForm()
return render_to_response('author_form.html', {'form': form})
update existing record
def authorcontactupd(request,id):
if request.method == 'POST':
a=Author.objects.get(pk=int(id))
form = AuthorForm(request.POST, instance=a)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
a=Author.objects.get(pk=int(id))
form = AuthorForm(instance=a)
return render_to_response('author_form.html', {'form': form})
All i can guess is that you are getting an object to fill a form, and trying to save it again.
Try using a ModelForm, and intantiate it with desired object.
It appears that your SlugField is returning None and because a null/blank slug already exists somewhere in the database, its giving an 'already exists' error. It seems like your slug field isn't saving correctly at all.