how to save Many to Many relationship in django - python

How to create an object for a Django model with a many to many field?
From above question i come to know we can save Many to Many field later only.
models.py
class Store(models.Model):
name = models.CharField(max_length=100)
class Foo(models.Model):
file = models.FileField(upload_to='')
store = models.ManyToManyField(Store, null=True, blank=True)
views.py
new_track.file = request.FILES['file']
new_track.save()
And file uploading working fine then later i modify my code to add store then i am here...
Now i am sure db return id's here. Then i tried with my below code but that's given me error only
x = new_track.id
new = Foo.objects.filter(id=x)
new.store.id = request.POST['store']
new.save()
ok so the error here is 'QuerySet' object has no attribute 'store'
And also i tried with add that's now working either.
So the question is how to save()

the right way of saving objects with manytomany relations would be:
...
new_track.file = request.FILES['file']
new_track.save()
new_store = Store.objects.get(id=int(request.POST['store']))
new_track.store.add(new_store)

As of 2020, here's my approach to saving ManyToMany Field to a given object.
Short Answer
class HostingRequestView(View):
def post(self, request, *args, **kwargs):
form = VideoGameForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.updated_by = request.user
obj.save()
selected_categories = form.cleaned_data.get('category') #returns list of all selected categories e.g. ['Sports','Adventure']
#Now saving the ManyToManyField, can only work after saving the form
for title in selected_categories:
category_obj = Category.objects.get(title=title) #get object by title i.e I declared unique for title under Category model
obj.category.add(category_obj) #now add each category object to the saved form object
return redirect('confirmation', id=obj.pk)
Full Answer
models.py
class Category(models.Model):
title = models.CharField(max_length=100, null=True, unique=True)
class VideoGame(models.Model):
game_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50, blank=False, null=False)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, on_delete=models.CASCADE)
category = models.ManyToManyField(Category) #ManyToMany Category field
date_added = models.DateTimeField(auto_now_add=True, verbose_name="date added")
forms.py ModelForm
class VideoGameForm(forms.ModelForm):
CATEGORIES = (
('Detective', 'Detective'),
('Sports', 'Sports'),
('Action', 'Action'),
('Adventure', 'Adventure'),
)
category = forms.MultipleChoiceField(choices=CATEGORIES, widget=forms.SelectMultiple())
class Meta:
model = VideoGame
fields = ['name', 'category', 'date_added']
views.py on POST
class HostingRequestView(View):
def post(self, request, *args, **kwargs):
form = VideoGameForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.updated_by = request.user
obj.save()
selected_categories = form.cleaned_data.get('category') #returns list of all selected categories e.g. ['Sports','Adventure']
#Now saving the ManyToManyField, can only work after saving the form
for title in selected_categories:
category_obj = Category.objects.get(title=title) #get object by title i.e I declared unique for title under Category model
obj.category.add(category_obj) #now add each category object to the saved form object
return redirect('confirmation', id=obj.pk)
URL path for redirect
urlpatterns = [
path('confirmation/<int:id>/', Confirmation.as_view(), name='confirmation'),
]
I hope this can be helpful. Regards

new.stores.all()
returns all stores linked to the object.

Maybe:
Change Foo to Tracks
Tracks.objects.filter(id=x) to Tracks.objects.get(id=x)
Let me know how it goes

why this confusion so much.. you are getting the id there then, call the store like
new_track.save()
new_track.store.add(request.POST['store'])

Related

Three-level inline formsets in the frontend

I'm trying to accomplish a three-level stacked inline form in Django. Suppose these models:
class Anuncio(models.Model):
title = models.CharField(max_length=200)
delivery = models.CharField(max_length=100)
class Product(models.Model):
anuncio = models.ForeignKey(Anuncio, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
quantity = models.PositiveIntegerField(default=1)
price = models.PositiveIntegerField()
class Image(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
image = models.ImageField()
There is a relation Anuncio-Product and another relation Product-Image. With this Django package, I accomplished exactly what I want in the Django admin: when creating an Anuncio object, I can add as many Products as I want, and those products can have as many Images as I want. I'm trying to accomplish this in the front end.
I think the way to go is with Django formsets, but I'm facing some problems. All the resources I've been able to find online are only 'two-level' formsets or in 'three-level' cases all the foreign keys point to the same parent model.
With this forms.py file:
class ProductForm(ModelForm):
class Meta:
model = Product
fields = ['name', 'quantity', 'price']
class ImageForm(ModelForm):
class Meta:
model = Imagen
fields = ['image']
class AnuncioForm(ModelForm):
class Meta:
model = Anuncio
fields = ['title', 'delivery']
And this views.py function:
def anunciocreateview(request):
form = AnuncioForm(request.POST or None)
ProductFormSet = inlineformset_factory(Anuncio, Product, form=ProductForm)
ImageFormSet = inlineformset_factory(Product, Image, form=ImageForm)
if all([form.is_valid(), ProductFormSet.is_valid(), ImageFormSet.is_valid()]):
parent = form.save(commit=False)
parent.anunciante = request.user
parent.save()
for form in ProductoFormSet:
child = form.save(commit=False)
child.anuncio = parent
child.save()
for form in ImagenFormSet:
imagen = form.save(commit=False)
imagen.product = form.product
imagen.save()
context = {
'form_1' : form,
'form_2' : ProductFormSet,
'form_3' : ImageFormSet,
}
But I think I'm missing important points when it comes to add the proper relations between models. This set-up gives an AttributeError of: 'ProductForm' object has no attribute '__name__'
The, for example, 'add (extra) Product' that appears in AdminStackedInLine I guess it can be accomplished with JavaScript, playing with hidden forms and changing attributes on click events.
Anyone has experience doing something similar or can guide me through the correct direction? Also on how to manage the data and the relations of the submitted forms?
I think your problem is you have tried to validate a class Form instead of instanciate your formset and validate them.
Your code would be look like to something like that :
def anunciocreateview(request):
ProductFormSet = inlineformset_factory(Anuncio, Product, form=ProductForm)
ImageFormSet = inlineformset_factory(Product, Image, form=ImageForm)
anuncio_form = AnuncioForm(request.POST or None)
product_formset = ProductFormSet(request.POST or None)
image_formset = ImageFormSet(request.POST or None)
if all([form.is_valid(), product_formset.is_valid(), image_formset.is_valid()]):
...
The function inlineformset_factory just create a Form class, not a instance of form.
More information and example on the documentation : https://docs.djangoproject.com/fr/4.1/topics/forms/formsets/

Save extra fields to django model apart from the fields which were present in django form(for the same model)

I have a Django model with few fields, In the django forms which is created using that django models. While using the forms.save method I also want to save the extra fields which were not present in Django forms but present in django models.
models.py
class NewProvisionalEmployeeMail(models.Model):
STATUS_CHOICES = (
(1, ("Permanent")),
(2, ("Temporary")),
(3, ("Contractor")),
(4, ("Intern"))
)
PAY_CHOICES = (
(1, ("Fixed")),
(2, ("Performance Based")),
(3, ("Not Assigned")),
)
POSITION_CHOICES = ()
for i, name in enumerate(Position.objects.values_list('position_name')):
POSITION_CHOICES += ((i, name[0]),)
email = models.EmailField(max_length=70, null=False, blank=False, unique=False)
token = models.TextField(blank=False, null=False)
offer_sent_by = models.CharField(max_length=50)
position_name = models.IntegerField(choices=POSITION_CHOICES, null=True, blank=True)
accepted = models.BooleanField(default=False)
name=models.CharField(max_length=30)
user_name=models.CharField(max_length=30)
pay = models.IntegerField(default=0)
title = models.CharField(max_length=25, null=True, blank=True)
pay_type = models.IntegerField(choices=PAY_CHOICES, default=3)
emp_type = models.IntegerField(choices=STATUS_CHOICES, default=1)
def __str__(self):
return str(self.offer_sent_by) +" to " + str(self.email)
def clean(self):
if(NewProvisionalEmployeeMail.objects.filter(email=str(self.email)).exists()):
NewProvisionalEmployeeMail.objects.filter(email=str(self.email)).delete()
def save(self, **kwargs):
self.clean()
return super(NewProvisionalEmployeeMail, self).save(**kwargs)
If you see it has following fields :
email, token, offer_sent_by, position_name, accepted, name, user_name, pay, title, pay_type, emp_type.
Now I only want the following fields in my forms :
email, position_name, name, user_name, pay, title, pay_type, emp_type and not token and offer_sent_by whose values will be determined in my views.py using some logic.
forms.py
class NewProvisionalEmployeeMailForm(ModelForm):
class Meta:
model = NewProvisionalEmployeeMail
fields = ['email', 'position_name',
'name', 'user_name', 'pay',
'title', 'pay_type', 'emp_type',
]
According to my logic in my views.py the other fields values are generated inside the function, but since to save the model we have to use formname.save, here it is NewProvisionalEmployeeMailForm.save(). However this will only save the fields which were coming from my template form, how do I also add other left fields while saving using this dunction.
views.py
def sendoffer(request):
context = {}
new_emp_form = NewProvisionalEmployeeMailForm();
context['form'] = new_emp_form
hostname = request.get_host() + "/dummyoffer"
if request.method=='POST':
new_emp_form = NewProvisionalEmployeeMailForm(request.POST)
if(new_emp_form.is_valid()):
token = VALUE COMES FROM LOGIC
offer_sent_by = VALUE COMES FROM LOGIC
# I also want to save the fields token, offer_sent_by in my models using this form save method
new_emp_form.save()
return render(request, 'mainapp/offer.html',context)
As you see new_emp_form save method will only save only those fields that are present in the form and not the fields token and offer_sent_by which is also part of the model. How do save the fields using form.save method?
Saving the form returns an instance of NewProvisionalEmployeeMail, so you can simply catch the returned object in a variable and set it's properties afterwards:
if(new_emp_form.is_valid()):
token = VALUE COMES FROM LOGIC
offer_sent_by = VALUE COMES FROM LOGIC
new_emp = new_emp_form.save(commit=False)
new_emp.token = token
new_emp.offer_sent_by = offer_sent_by
new_emp.save()
for the first time we can change it as following
new_emp = new_emp_form.save(commit=False)
so that i wont save to the database.

Django form custom field

If we have a modelForm with some fields not directly corresponding to the model, how do we have the form process them in a custom way, while saving the rest of fields as by default?
For example, we have a model for an item that supports multilingual descriptions. The models are:
class Item(models.Model):
name = models.ForeignKey(Localization)
on_sale = models.BooleanField(default=False)
class Localization(models.Model):
de = models.TextField(blank=True, null=True, verbose_name='de')
eng = models.TextField(blank=True, null=True, verbose_name='eng')
The form to add/edit an Item looks like that:
class ItemForm(forms.ModelForm):
id = forms.CharField(widget=forms.HiddenInput(), max_length=128, label='')
name_eng = forms.CharField(widget=forms.TextInput(attrs={'style': 'width:200px;'}), label='eng')
name_de = forms.CharField(widget=forms.TextInput(attrs={'style': 'width:200px;'}), label='de')
on_sale = forms.CharField(widget=forms.CheckboxInput(), label='on_sale', )
class Meta:
model = Item
fields = ('id', 'on_sale',)
Now what saving this form should do, is for a new Item - create Localization object with the two name fields, then create an Item object with on_sale field, linked to Localization object. For an existing Item - edit the corresponding Localization object and then on_sale field of the Item itself.
I did the task with a separate function, that processes the custom fields from the request separately, but having it all done by the form's save() method looks better. Or am I wrong?
PS I'm sorry to be asking an evidently worn question, but I simply fail to do what I want with other examples.
Update:
I actually got it working the way I wanted with the help of the hints from here. Code goes as that, please let me know if it can be optimized.
class NameForm(forms.ModelForm):
# id = forms.CharField(widget=forms.HiddenInput(), max_length=128, label='')
id = forms.CharField(widget=forms.TextInput(attrs={'style': 'width:200px;'}), label='id', required=False)
name_eng = forms.CharField(widget=forms.TextInput(attrs={'style': 'width:200px;'}), label='eng')
name_de = forms.CharField(widget=forms.TextInput(attrs={'style': 'width:200px;'}), label='de')
gender = forms.CharField(widget=forms.CheckboxInput(), label='gender', required=False)
class Meta:
model = Name
fields = ('id', 'gender',)
def save(self):
instance = super(NameForm, self).save(commit=False)
obj_id = self.cleaned_data['id']
if obj_id:
instance_bd = Name.objects.get(pk=obj_id)
loc = instance_bd.name
loc.de = self.cleaned_data['name_de']
loc.eng = self.cleaned_data['name_eng']
loc.save()
instance.id = obj_id
else:
loc = Localization(de=self.cleaned_data['name_de'], eng=self.cleaned_data['name_eng'])
loc.save()
instance.name = loc
instance.save()
return instance
The view is simply
#login_required
def admin_lists(request):
if request.method == 'POST':
form = NameForm(request.POST)
if form.is_valid():
form.save()
forms = {'name': NameForm()}
return render(request, 'admin/lists.html', {'forms': forms})
In this case is better don't use the forms'save method, try this
if request.method == 'POST':
form = ItemForm(request.POST)
# check whether it's valid:
if form.is_valid():
post = request.POST
set_name_eng= post['name_eng']
set_name_de= post['name_de']
set_on_sale = post['on_sale']
#now here we create the anothers objects
a = Localization(de=set_name_de, eng=set_name_eng)
a.save()
b = Item(name=a, on_sale=sale)
b.save()
I didn't understand the last part, but I think you need to organize better your models and forms, Let me know and I'll try to help you

Django: 'Thoughts' object has no attribute 'choice_set' - Trouble accessing a choice set

In my model I define a Thoughts and Comments model. One thought has many Comments as so:
class Thoughts(models.Model):
name = models.CharField(max_length=30)
thought = models.CharField(max_length=500)
class Comments(models.Model):
name = models.CharField(max_length=30)
comment = models.CharField(max_length=200)
original_post = models.ForeignKey(Thoughts, default=0)
On my site, when you go to view a thought, I want all of the comments to appear. It is my understanding that you can use choice_set to access attributes via one-to-many relationship. Here's my view:
def thought(request, thought_num):
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
c = Comments.objects.create(name=form.cleaned_data['name'],
comment=form.cleaned_data['comment'])
c.save()
else:
form = CommentForm()
get_post = Thoughts.objects.get(pk=thought_num)
comments = get_post.choice_set.all()
return render(request, 'thought.html', {'form': form, 'comment':comments,})
In these lines, I attempt to access all comments related to a particular thought in order to print them in my template.
get_post = Thoughts.objects.get(pk=thought_num)
comments = get_post.choice_set.all()
When I access the page that should display the comments, I get this error:
Exception Type: AttributeError
Exception Value:'Thoughts' object has no attribute 'choice_set'
Perhaps I am missing something, I am not sure. I'm sure it's something simple. Thanks for your time
To retrieve all the Comments related to a Thought. You can do the following:
Thoughts.objects.get(pk=thought_num).comments_set.all()
If you would like to override the default related_name ("comments_set"). You can do the following:
original_post = models.ForeignKey(Thoughts, default=0, related_name='choice_set')
When you make a ForeignKey the default related name becomes the lower case name of the current class + "_set" so for your project should be:
get_post = Thoughts.objects.get(pk=thought_num)
comments = get_post.comments_set.all()
Or you could even create a custom related name instead of the default:
class Thoughts(models.Model):
name = models.CharField(max_length=30)
thought = models.CharField(max_length=500)
class Thoughts(models.Model):
name = models.CharField(max_length=30)
thought = models.CharField(max_length=500)
class Comments(models.Model):
name = models.CharField(max_length=30)
comment = models.CharField(max_length=200)
original_post = models.ForeignKey(Thoughts, default=0, related_name='comments')
so you can get your comments like this:
get_post = Thoughts.objects.get(pk=thought_num)
comments = get_post.comments.all()

Django how to set initial object to ModelList?

I've created a simple model
class Person(models.Model):
name = models.CharField(max_length=25)
surname = models.CharField(max_length=25)
birth_date = models.DateField()
...
and a simple ModelForm
class PersonForm(ModelForm):
class Meta:
model = Person
how can I put a Person object that I already have (e.g. Person.objects.get(pk=1)) to become an initial value for the form? I need to edit this object
From the docs:
# Creating a form to add an article.
>>> form = ArticleForm()
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
so in your case, in your views.py:
person = Person.objects.get(pk=1)
form = PersonForm(instance=person)
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelform

Categories