ValueError: Cannot assign value: must be an instance - django

I have four fields in a model, one of which is a foreign key field and the other three are m2m fields. The form is opened in the modal, but the data is not being saved, Error given below. I don't understand what I did wrong. I would be very grateful for a little help.
Model:
class ProductAttributes(models.Model):
product = models.ForeignKey('Product', blank=True, null=True, on_delete=models.SET_NULL)
size = models.ManyToManyField('ProductSize', blank=True)
colour = models.ManyToManyField('ProductColour', blank=True)
cupsize = models.ManyToManyField('ProductCupSize', blank=True)
def __str__(self):
return self.product
Form:
class ProductAttributesForm(forms.ModelForm):
product = forms.IntegerField(label=('ID'),required=True, disabled=True)
size = forms.ModelMultipleChoiceField(queryset=ProductSize.objects.all(),widget=Select2MultipleWidget, required=False)
colour = forms.ModelMultipleChoiceField(queryset=ProductColour.objects.all(),widget=Select2MultipleWidget, required=False)
cupsize = forms.ModelMultipleChoiceField(queryset=ProductCupSize.objects.all(),widget=Select2MultipleWidget, required=False)
class Meta:
model = ProductAttributes
fields = ['product','size','colour','cupsize']
Template:
{% load crispy_forms_tags %}
<form id="Form" method="post" action="{% url 'accpack:products_attributes_create' product %}" class="js-product-create-form col s12" >
{% csrf_token %}
{% crispy form form.helper %}
</form>
View:
def save_attribute_form(request, form, template_name, pk):
data = dict()
if request.method == 'POST':
if form.is_valid():
form.save()
data['form_is_valid'] = True
else:
data['form_is_valid'] = False
context = {'form': form, 'product':pk}
data['html_form'] = render_to_string(template_name, context, request=request)
return JsonResponse(data)
def attribute_create(request, pk):
if request.method == 'POST':
form = ProductAttributesForm(request.POST, initial={'product': pk})
else:
form = ProductAttributesForm(initial={'product': pk})
return save_attribute_form(request, form, 'main/products/partial_product_attribute_form.html', pk)
ajax:
var saveForm = function () {
var form = $(this);
$.ajax({
url: form.attr("action"),
data: form.serialize(),
type: form.attr("method"),
dataType: 'json',
success: function (data) {
if (data.form_is_valid) {
$("#modal-product_attribute").modal("hide");
console.log(data.form_data);
}
else {
$("#modal-product_attribute .modal-content").html(data.html_form);
}
}
});
return false;
$("#modal-product_attribute").on("submit", ".js-product-create-form", saveForm);
error:
File "C:\ProgramData\Anaconda3\envs\djangoproject\lib\site-packages\django\db\models\fields\related_descriptors.py", line 220, in __set__
self.field.remote_field.model._meta.object_name,
ValueError: Cannot assign "111": "ProductAttributes.product" must be a "Product" instance.

You have an error in your forms.py.
class Meta:
model = ProductSize
fields = ['product','size','colour','cupsize']
Here, the model should be
model = ProductAttributes
Since the model you specified here ProductSize does exist, submitting form will just create another instance to ProductSize model without those specific fields and error messages. You can check your admin page.
EDIT ******
2. Based on what you added in the question and the error message, it looks like in attribute_create you are passing product's pk. Now in the attribute_create, you are passing initial dict {'product': pk}. This is wrong, because, in your ProductAttributesForm, the product field should be an instance rather than a pk.
You need to refer product as
product = Product.objects.filter(pk=pk)
initial = {'product':product}
The current error message will be gone, but you will have other errors.

Related

Django ModelForm saves and fetches <QuerySet object> instead of values

I have a simple Django 3.1.0 app I need to create in order to assign Tasks with Tags (or assign tags into tasks).
Model
class Task(models.Model):
user = models.CharField(max_length=33)
time = models.DateTimeField(auto_now_add=True)
task = models.CharField(max_length=500)
tags = models.CharField(max_length=100, default="None", null=True)
class Tag(models.Model):
tag = models.CharField(max_length=30, default="No Tag")
members = models.ManyToManyField('Task', related_name="tag")
class Meta:
verbose_name = "tag"
verbose_name_plural = "tags"
view
def main(request):
model = Task.objects.values().all()
tags = Tag.objects.values().all()
form = TaskForm()
con = {'context': list(model), 'form': form, 'tags': list(tags)}
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
form = TaskForm()
return render(request, "tasks.html", con)
form
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ['user', 'task', 'tags']
template_name = 'tasks.html'
tags = ModelMultipleChoiceField(
queryset= Tag.objects.all(),
widget=CheckboxSelectMultiple(), required=False,
)
task_form
<form method="post" class="form">
{% csrf_token %}
{{form}}
<input type="submit" value="Save">
</form>
This returns in the tags list the items listed as:
Tag object (1)
Tag object (2)
And when it saves when i press submit, it fetches in a table (in another template), the values saved in the text of <QuerySet [<Tag: Tag object (2)>]>
That's how it stores them in the database.
I have managed to extract the values as they are ('jenkins','AKS') and send them in the template using this (bootstrapvuejs) : {% for tag in tags %}<b-form-checkbox>{{tag.tag}}</b-form-checkbox>{% endfor %}, which lists them raw values perfectly.
However, when I do that modification, the form submitted is not written to database.
What am I missing?
UPDATE!
I have partly solved it by adding this into the Tag model:
def __str__(self):
return self.tag
but when it persists it on submit, it still saves it as:
<QuerySet [<Tag: jenkins>]>
So, how and where do I strip only the specific tag values to be inserted in the database?
Many Thanks
Alright so there is a couple issues with your code, first off your main view:
Change it from this:
def main(request):
model = Task.objects.values().all() # calling values without specifying an argument makes no sense so just call it like **Task.objects.all()**
tags = Tag.objects.values().all() # same here
form = TaskForm() # don't call your form here it gets reassigned later anyways
con = {'context': list(model), 'form': form, 'tags': list(tags)} # don't define your context here since you are reasigning your form later so the form instance is always TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
form = TaskForm()
return render(request, "tasks.html", con)
To this:
def main(request):
model = Task.objects.all()
tags = Tag.objects.all()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
form = TaskForm()
context = {'tasks': model,
'form': form,
'tags': tags}
return render(request, "tasks.html", con)
Then in your template pass your form with as_p method call:
{{ form.as_p }}
Hovewer the error you are getting is not because of your html or your view, it's because your tags field in your Task model is not a ManyToMany relationship to your Tag model but rather a simple CharacterField and you are trying to save objects to the CharField, so rewrite your Task model like this:
class Task(models.Model):
user = models.CharField(max_length=33)
time = models.DateTimeField(auto_now_add=True)
task = models.CharField(max_length=500)
tags = models.ManyToMany(Tags)
Then your form should save them in the tags field of your Task instance and you can view them like this:
task = Task.objects.get(pk=1)
task_tags = task.tags.all() # stores a queryset of all tags of the queried task
and in the template:
{% for tag in task.tags.all %}
...
{% endfor %}
OK , I solved the POST data that is saved in database as Queryset, by extracting in the view where save() is called, the field 'tags' likewise:
f = form.save(commit=False)
f.tags = request.POST['tags']
form.save()
The only problem now is that I have multiple checkboxes in the form but this way it extracts only one of them, whilst I would expect it to return a list like what is printed in the request.POST : <QueryDict: {'csrfmiddlewaretoken': ['XV7HgTFiWXEnrkhqT3IsqUN2JbnT7YIH5r6fKgh2ehqeLsLMpvCPdUU4N2qwWuPk'], 'user': ['afa'], 'task': ['aff'], 'tags': ['jenkins', 'AKS']}> -> from that I call 'tags' but it saves only 'jenkins' ...
UPDATE
OK, I RTFM and saw that there is a method on the QueryDict object that can be passed to request.POST.getlist('tags') , so now it returns the complete value of 'tags' key.

Difference between args=[topic.id] and args=[topic_id] when using the return HttpResponseRedirect(reverse) in Django

I'm following a tutorial from to build a simple blog app with Django.
I have noticed that in the new_entry() view, we need to pass topic_id in agrs when using the reverse function:
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST':
#No data submitted, create a blank form
form = EntryForm()
else:
#POST data submitted; process data
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
new_entry.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
context = {'topic': topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
However, when creating the edit_entry() view (that allows users to edit existing entries), we need to pass topic.id
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != 'POST':
#Initial request, render the form with current entry
form = EntryForm(instance=entry)
else:
#Submit changes, process data
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
context = {'topic':topic, 'entry':entry, 'form':form}
return render(request,'learning_logs/edit_entry.html', context)
Initially I thought this was a mistake so I used args=[topic_id] in both reverse functions and it worked fine
Later, I decided I wanted to add a title to each entry so I made some minor changes to models.py, migrated those changes to the database and then changed the templates to include {{entry.title}} in them.
Basically, all I did was add this code to models.py
title = models.CharField(max_length=200, default='Add a title')
models.py:
class Topic(models.Model):
"""A topic the user is learning about"""
text = models.CharField(max_length = 200)
date_added = models.DateTimeField(auto_now_add = True)
def __str__(self):
"""Return a string representation of the model"""
return self.text
class Entry(models.Model):
"""A blog post about a particular topic"""
topic = models.ForeignKey(Topic)
title = models.CharField(max_length=200, default='Add a title')
text = models.TextField()
date_added = models.DateTimeField(auto_now_add = True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Return a string representation of the model"""
char_numb = len(self.text)
if char_numb > 50:
return self.text[:50] + "..."
else:
return self.text
forms.py:
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text','title']
labels = {'text':'', 'title': ''}
widgets = {'text': forms.Textarea(attrs={'cols': 80})}
After adding these changes, I got the following error when I tried to edit an entry's default title:
NameError at /edit_entry/4/
global name 'topic_id' is not defined
I changed args=[topic_id] to args=[topic.id] in the views.py file edit_entry() view and now it works fine, any idea why this is the case? What difference is there between topic_id and topic.id in this context?
This is the edit_entry.html template in case it makes any difference:
{% extends "learning_logs/base.html" %}
{% block content %}
<h1>{{topic}}
</h1>
<p>Edit your entry</p>
<form action = "{% url 'learning_logs:edit_entry' entry.id %}" method
= 'post'>
{% csrf_token %}
{{ form.as_p }}
<button name = "submit">save changes</button>
</form>
{% endblock content %}
Thanks in advance for any advice
In your first view, you have topic_id from the url and you fetch topic from the database on the first line, so you can use either topic_id or topic in the view.
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
In the template context for they view, you set topic but not topic_id. Therefore you can only use topic.id in the template.
context = {'topic': topic, 'form': form}
In your second view, you get entry_id from the url and get topic via the entry. You don’t set topic_id anywhere so you must use topic.
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic

send arguments to django forms

I want to pre-populate a textfield on a django form when the form is loaded. Below is my code.
I want this "content" field in forms.py to be pre-populated when my form loads in the browser. This field should be un-editable. But with the code below an empty textfield gets created.
I know I can pre-populate this info by sending it in the url string just like I am sending 'id'. But I don't want to take that approach. Is there any other way to send parameters to forms?
forms.py
class ContentModelForm(ModelForm):
content = forms.CharField(max_length=256)
message = forms.CharField(max_length=256)
created_at = forms.DateTimeField('DateTime created')
class Meta:
model = Content
def __init__(self, *args, **kwargs):
super(ContentModelForm, self).__init__()
self.fields['content'].value = kwargs.get('content')
views.py
def post_form_upload(request, id):
post = get_object_or_404(models.Post, id=id)
content = post.content
if request.method == 'GET':
form = ContentModelForm(content = content)
else:
form = ContentModelForm(request.POST)
if form.is_valid():
message = form.cleaned_data['message']
created_at = form.cleaned_data['created_at']
models.Content.objects.create(post_id = id,
message = message,
created_at = created_at)
return HttpResponseRedirect(reverse('post_form_upload',
args = (post.next_id,)))
return render(request, 'survey_forms/post_form_upload.html',{
'form':form,
'id' : id,
})
survey_forms/post_form_upload.html
<form action="{% url 'post_form_upload' id=id %}" method='post'>
{% csrf_token %}
{{form.as_p}}
<input type='submit' value='Submit'/>
</form>
Thanks.
You should be using the initial keyword argument when instantiating your form. This accepts a dictionary where the keys are the field name and the values are the initial values.
form = ContentModelForm(initial={'content': content})
Moreover since you want this field to be readonly. You should change this line
content = forms.CharField(max_length=256)
to
content = forms.CharField(max_length=256, widget=forms.TextInput(attrs={'readonly':'readonly'})
if you want an input to be pre-populated then you might as well just give it a "value" attribute, for example.
input<type="text, value="content_you_want_in_there">

Django Model form is not shown

I am probably missing something very simple, because my model form is not shown at the template. The code is very simple:
models.py:
class Story(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=200)
content = models.TextField()
picture = models.ImageField(upload_to = 'images/post_images')
date = models.DateTimeField(auto_now_add=True)
forms.py:
class StoryForm(forms.ModelForm):
class Meta:
model = Story
views.py:
from sfv.forms import StoryForm
#login_required(redirect_field_name=None)
def restricted(request):
user = request.user
form = StoryForm()
#graph = get_persistent_graph(request)
return render(request, "restricted.html", {user : 'user', form : 'form',})
template:
<form method = 'POST' action = ''>
<table>
{{ form }}
<table>
</form>
I have also tried form.as_p, that didnt help.
Change your render method from
return render(request, "restricted.html", {user : 'user', form : 'form',})
to
return render(request, "restricted.html", { 'user' : user, 'form' : form})
The key and value in the context dictionary were interchanged. Hence the issue.

Django. How to saves data in two forms

I have two forms:
class Form_registration_security (ModelForm):
class Meta:
model = Security
fields = ['fk_id_users_security', 'e_mail', 'password']
widgets = {
'e_mail': forms.TextInput(attrs = {'placeholder': 'Your Email'}),
'password': forms.TextInput(attrs = {'placeholder': 'New Password'}),
}
class Form_registration_user (ModelForm):
class Meta:
model = Users
fields = ['id', 'first_name', 'last_name', 'date_birthdaty']
widgets = {
'id': forms.TextInput(attrs = {'placeholder': 'id'}),
'first_name': forms.TextInput(attrs = {'placeholder': 'First Name'}),
'last_name': forms.TextInput(attrs = {'placeholder': 'Last Name'}),
'date_birthdaty': forms.TextInput(attrs = {'placeholder': 'Date'})
}
But data saves only in one mode - (Form_registration_user).
Code in view:
def save_registration (request ):
if request.method == 'POST':
form_user = Form_registration_user(request.POST)
form_security = Form_registration_security(request.POST)
if form_user.is_valid() and form_security.is_valid():
data_user = form_user.save()
data_security = form_security.save(commit=False)
data_security.data_user = data_user
data_security.save()
return render_to_response('see_you_later.html')
else:
return render_to_response('error.html')
I'm always see - error.html, although I'm fill right form.
Model User have a primary key.
Model Security have a foreign key.
My template:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div class="entry_or_register">
{% load staticfiles %}
<img src="{% static "tumblr.gif" %}" width="1250" height="550">
<form name="registration" method="post" action="save/">
{% csrf_token %}
{{ form_registration_user.as_p }}
{{ form_registration_security.as_p }}
<input type="submit" value="SignUp">
</form>
</div>
</body>
</html>
Thanks for help! Right function:
def save_registration (request ):
if request.method == 'POST':
form_user = Form_registration_user(request.POST)
form_security = Form_registration_security(request.POST, request.FILES)
if form_user.is_valid():
data_user = form_user.save()
data_security = form_security.save(commit=False)
data_security.data_user = data_user
data_security.save()
return render_to_response('see_you_later.html')
else:
return render_to_response('error.html')
You should post also the html markup of the corresponding template.
Anyway, here's a view i used once i had to save data from two ModelForms in the same page, with the user clicking a single submit button:
def register(request):
message = None
if request.method == 'POST':
user_form = NewUserForm(request.POST)
details_form = UserDetailsForm(request.POST, request.FILES)
if user_form.is_valid():
new_simple_user = user_form.save()
message = _("User inserted")
if details_form.is_valid():
# Create, but don't save the new user details instance.
new_user_details = details_form.save(commit=False)
# Associate the user to the user details
new_user_details.user = new_simple_user
# save a new user details instance
new_user_details.save()
message = _("User details inserted")
else:
user_form = NewUserForm()
details_form = UserDetailsForm()
return render_to_response('register.html', { 'user_form': user_form, 'details_form': details_form, 'message': message,},\
context_instance=RequestContext(request))
I'm not sure how you rendered your forms in the template, but it could be that when you click submit, only one of the forms sends its data in the HTTP request.
Then the other form's constructor won't find its key in the POST variable and the outcome won't be a valid form. I think that's why you test always fail.
Now, I hope you could give us some more details on what you're trying to do but I think you are going to need a custom Form class (that would be the union of your two current forms) instead of a ModelForm.
EDIT : sorry, you shouldn't actually need to do that...
Good luck.