Django Use ManyToManyField Data in View - django

models.py
class Profile(models.Model):
profile_name = models.CharField(max_length = 255, blank = False)
extra_profile_text = models.CharField(max_length = 50, blank = False)
class Category(models.Model):
category_name = models.CharField(max_length = 50, blank = False)
extra_category_text = models.CharField(max_length = 50, blank = False)
class ProfileCategory(models.Model):
profile = models.ManyToManyField(Profile)
category = models.ManyToManyField(Category)
forms.py
class ProfileCategoryForm(forms.ModelForm):
class Meta:
model = ProfileCategory
fields = ('profile', 'category',)
views.py
def task(request):
if request.method == "POST":
form = ProfileCategoryForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
#use [profile_name, extra_category_text data] that user selected
post.save()
form.save_m2m()
return redirect('somewhere')
else:
form = ProfileCategoryForm()
context = {'form': form }
return render(request, 'some_app/somewhere.html', context)
I want to bring 'profile_name', 'extra_category_text' datas in view when user select from ProfileCategoryForm.
process will be
Front: user select one of Profile, one of Category > Save
Back: get user selected Profile, Category datas(ex: profile_name, extra_profile_text) > do some task > Save to ProfileCategory model.
it seems that I need to use queryset but no clue at all :(

Like #gitblame said, you create such a complicate models.
You can change the model to easier query.
Front: user select one of Profile, one of Category > Save
We do it normally, return data to html, show it and take care of it.
# You can create js file and capture event of class/id changed and request to api server
#(if you want to check like: 1 Profile and can select only Category relate only)
Back: get user selected Profile, Category datas(ex: profile_name, extra_profile_text) > do some task > Save to ProfileCategory model.
You can do it like, check when the form submit to views.py or check in model (override the save method, anything else...)
link to django override method

Related

"user" is not set when parsing a data-dict to a Django form, but all other fields are

Say I have the following
#models.py
class MyModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE,null=True)
link = models.URLField(max_length=2048)
date_added = models.DateTimeField(default = timezone.now)
used = models.BooleanField(default = True)
and my form
class MyModelForm(forms.ModelForm):
class Meta:
model = Wishlist
exclude = [
"user","date_added"
]
If I try to create an instance like (note, I have omitted the if request.method="POST" etc. for clarity)
def myView(request):
user = request.user
instance = MyModel(user=user)
data = {"link":link,"date_added":domain,"user":user}
form = MyModelForm(data=data)
form.save()
the link and date_added is written to the database, but the user field is empty.
If I set the user to the instance after the form is created like
def myView(request):
user = request.user
instance = MyModel(user=user)
data = {"link":link,"date_added":domain}
form = MyModelForm(data=data)
form.save(commit=False)
form.instance.user = user #Setting the user here
form.save()
It works like a charm.
How come, I cannot set the user in the data-dict, and is there a way to do so?
How come, I cannot set the user in the data-dict.
A form will ignore all fields that it does not have to process. This makes the webserver more secure: here we ensure that the user can not create a MyModel for another user.
If thus a field belongs to the excluded attribute, or not in the fields attribute from the Meta class, then the form will not set the corresponding model object attribute.
You did not specify the instance to the form:
def myView(request):
user = request.user
instance = MyModel(user=user)
data = {"link":link,"date_added":domain}
form = MyModelForm(data=data, instance=instance)
form.save()

"user_id" is empty when setting MyForm(data=data) (Django)

I have the following model
#models.py
class MyModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE,null=True)
link = models.URLField(max_length=2048)
date_added = models.DateTimeField(default = timezone.now)
used = models.BooleanField(default = True)
and my form
class MyModelForm(forms.ModelForm):
class Meta:
model = Wishlist
exclude = [
"user","date_added"
]
when I in my view try to "manually" create an instance (in the for-loop) and save it, the "user_id" in my database is NULL
#views.py
def AddLink(request):
user = request.user
instance = MyModel(user=user)
if request.method == "POST":
form = MyModelForm(request.POST,instance = instance)
if form.is_valid():
link = form.instance.wishlist
used = form.instance.discount_pct
#some util function to do some stuff
res = my_util_func(link,api_key)
for used, link in zip(res[0],res[1]):
data = {"link":link,"used":used}
form = MyModelForm(data=data)
form.save()
context = {
"results":res}
return render(request, "discounttracker/myhtml.html",context=context)
I have tried changing data to data = {"link":link,"used":used,"user":user} and data = {"link":link,"used":used,"user_id":user} but it is still empty.
How can I add the user here?
There is a secret option in ModelForm.save() called commit that allows you to not save the form right now, but instead return a model and insert some data into it. This should work perfectly here
form = MyModelForm(data=data))
instance = form.save(commit=False)
instance.user = request.user
instance.save()
This sample inserts the currently logged in user into the user field on your model.

Django ForeignKey Model Form

I'm newbie on Django. I have two model and one of this model have Foreign Key. I'm using Model Form in forms and when I fill the form my foreign key field return null. What I want is when I fill the form foreign key field, fill according to the pointed out by the foreign key.
Models:
class customerInfo(models.Model):
customerName = models.CharField(max_length = 50)
customerContent = models.TextField(max_length = 50)
createdDate= models.DateTimeField(auto_now_add = True)
def __str__(self):
return self.customerName
class productInfo(models.Model):
username = models.CharField(max_length = 50)
passwd = models.CharField(max_length = 50)
destIp = models.CharField(max_length = 50)
hostname = models.CharField(max_length = 50)
productName = models.CharField(max_length = 50)
customer = models.ForeignKey(customerInfo,on_delete = models.CASCADE,null=True)
def __str__(self):
return self.productName
Forms:
class customerForm(forms.ModelForm):
class Meta:
model = customerInfo
fields = (
"customerName",
)
class addProductForm(forms.ModelForm):
class Meta:
model = productInfo
fields = (
"productName",
)
class productInfoForm(forms.ModelForm):
class Meta:
model = productInfo
fields = (
"username",
"passwd",
"destIp",
"hostname",
)
Views:
#login_required(login_url = "/")
def addCustomer(request):
form = customerForm(request.POST or None)
content = {"form" : form,}
if form.is_valid():
form.save()
customerName = form.cleaned_data['customerName']
return redirect("addproduct")
else:
return render(request,"addcustomer.html",content)
#login_required(login_url = "/")
def addProduct(request):
form = addProductForm(request.POST or None)
content = {"form" : form}
if form.is_valid():
global productName
productName = form.cleaned_data['productName']
return redirect("addproductinfo")
return render(request,"addproduct.html",content)
#login_required(login_url = "/")
def addProductInfo(request):
form = productInfoForm(request.POST or None)
content = {"form" : form}
if form.is_valid():
p = form.save(commit = False)
p.productName = productName
p.save()
return redirect("customer")
return render(request,"addproductinfo.html",content)
As a result, I want to see the customer's products when I click on the customer name. Not all products.
Before I can do that, the customer id fields needs to be full.
I hope you understood me.
Your question and code sample is not clear.
First of all you should break down your model into several use cases:
Customer: list of customers, Create, Read, Update & Delete (CRUD) customer
Product: list of products, Create, Read, Update & Delete (CRUD) product
From the list of customers you can Read one and on the 'detail view displayed' you can Create, Update or Delete it.
From the list of products you can Read one and on the 'detail view displayed' you can Create, Update or Delete it.
Passing from the list of customer to the list of product can be done via an extra Button/Link displayed per line on your Customer List, so as your Button/Link used to display any Customer Detail.
The customer PrimaryKey (PK) is passed to the detail via the url definition.
path('customer/<pk>', views.customer_detail_view, name='customer_detail'),
This url is only for display. You're also need one for each DB operation: Create, Update, Delete. Find below urls.py code example for your customer. You'll need the same for the products.
from django.urls import path
from . import views
urlpatterns = urlpatterns + [
path('customer', views.customer_list_view, name='customer_list'),
path('customer/add', views.customer_add_view, name='customer_add'),
path('customer/<pk>', views.customer_detail_view, name='customer_detail'),
path('customer/<pk>/upd', views.customer_update_view, name='customer_update'),
path('customer/<pk>/del', views.customer_delete_view, name='customer_delete'),
]
Note that create doesn't pass 'pk' since it is unknown yet...
The call to the Detail View from the List View is done in your html template
<tbody>
{% for i in customer_list %}
<tr>
<td>{{ i.customerName }}</td>
<td>{{ i.customerContent|default_if_none:"" }}</td>
</tr>
{% endfor %}
</tbody>
The argument is passed by kwargs (dict) via the url and if you use ClassBasedView (generic.DetailView) it will be handled automatically. If not, you have to grab the kwargs like: kwargs.get('pk') or kwargs.pop('pk') the last one remove 'pk' from the kwargs. You could also pass the 'pk' using args (no pk key assignement) {% url 'customer_detail' i.id %}. This can also be defined directly in a get_absolute_url function of your model.
def get_absolute_url(self):
return reverse_lazy('customer_detail', args=[str(self.id)])
or
def get_absolute_url(self):
return reverse_lazy('customer_detail', kwargs={'pk': self.pk})
By doing that way you'll also be able to manage your 'productName' global variable, which should be avoided! By the way I don't understand why you're willing to separate the creation of productName and productInfo??? Why not keeping them all together?
Finally, if you want to display several possible encoding line for your Product, you should take a look at Django-FormSet. Search google for FormSet Tutorial but this is more an advanced feature.
A ProductFormset with 5 possible encoding lines would look like:
from django.forms import modelformset_factory
ProductFormset = modelformset_factory(
productInfo,
fields=('productName', ),
extra=5,
widgets={'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter product Name here'
})
}
)
If you want to reuse the productInfo model then you shoud you models.ManyToManyField instead of the ForeignKey. As i understand correctly you want to have a product that multiple of the customers can "connect" to , right ?
for more --> https://docs.djangoproject.com/en/2.1/ref/models/fields/
and more --> https://www.revsys.com/tidbits/tips-using-djangos-manytomanyfield/
My usage:
class EventVocab(models.Model):
word = models.CharField(max_length = 30)
word_chinese = models.CharField(max_length = 30,blank=True, null=True)
explanation = models.TextField(max_length = 200)
example = models.TextField(max_length = 100)
word_audio = models.FileField(blank=True, null=True)
explanation_audio = models.FileField(blank=True, null=True)
example_audio = models.FileField(blank=True, null=True)
class UserVocab(models.Model):
event_vocab = models.ManyToManyField(EventVocab, related_name='event_vocab')
current_user = models.ForeignKey(User, related_name="vocab_owner", on_delete=models.CASCADE)
In this example UserVocab (in your case product) can be connected to just one User, but one user can have multiple event_vocabs (products)

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.

Substituting an absent field value when saving a modelform

I have a this model:
class Fleet(models.Model):
company = models.ForeignKey("Company", editable=False)
aircraft = models.ForeignKey("Aircraft")
size = models.IntegerField(default=1)
description = models.TextField(blank=True)
def __unicode__(self):
return u"%s" % (self.aircraft, )
And then a form based on this model:
class FleetForm(ModelForm):
class Meta:
model = Fleet
exclude = ('company', )
When I use this form in a template, the "company" field is not added, which is expected. But that field is required as blank != True.
The way I use this form, the company attribute will always be known in the view function, as it's passed from the URL. How can I add the company to the form in the view function before I save it?
Here is my view:
def new_fleet(request, company_id):
from forms import FleetForm
company = Company.objects.get(pk=company_id)
if request.method == "POST":
form = FleetForm(request.POST,)
form.company = company #doesn't work
form = form.save(commit=False) #can't do this because the form
form.company = company #doesn't validate (company is not set)
if not form.errors:
form.save()
else:
fleet = Fleet(company=company) #pointless because the company widget
form = FleetForm(instance=fleet) #isn't created but eh whatever
There are two ways to solve this issue:
Instantiate your model with initial values for the missing, but required fields:
company = Company.objects.get(pk=company_id)
fleet = Fleet(company=company)
form = FleetForm(request.POST, instance=fleet)
new_fleet = form.save()
Use save(commit=False) and manually set any extra required fields:
company = Company.objects.get(pk=company_id)
form = FleetForm(request.POST)
fleet = form.save(commit=False)
fleet.company = company
new_fleet = fleet.save()
See the note in this section of the ModelForm API documentation for more details.
By the way, either editable=False or exclude is enough to remove a field from a ModelForm; you don't need both.
in #Ayman Hourieh 's answer . Just to address a pitfall in Django. If you have many-to-many field in the form. it would not get saved here. You should explicitly call save_m2m() . add one more line as below.
form.save_m2m()