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)
Related
I want to create a new site and add corresponding publications at the same time. I have to use a custom made form for the "site" due to the large dataset linked to it through the "municipality" foreign key.
I have these models:
class site(models.Model):
sid = models.AutoField(primary_key=True)
site_name = models.CharField(max_length=250)
site_notes = models.CharField(max_length=2500, blank=True, null=True)
municipality = models.ForeignKey('local_administrative_unit', on_delete=models.PROTECT)
geom = models.PointField(srid=4326)
def __str__(self):
return '{}, {} ({})'.format(self.sid, self.site_name, self.municipality)
lass cit_site(models.Model):
cit_site_id = models.AutoField(primary_key=True)
publication = models.ForeignKey('publication', on_delete=models.PROTECT)
site = models.ForeignKey('site', on_delete=models.PROTECT)
first_page = models.IntegerField(null=True, blank=True)
last_page = models.IntegerField(null=True, blank=True)
notes = models.CharField(max_length=500, null=True, blank=True)
def __str__(self):
return '{}: {} - {}'.format(self.publication.pub_id, self.first_page, self.last_page)
The site form at the moment just adds a site through a class based view. Because of the large dataset of municipalities, loading the form would take forever and it wouldn't be very handy to actually choose the right municipality (16k+ records in this table atm), so i made this custom form:
class NewSiteForm(DynamicFormMixin, forms.Form):
def land_choices(form):
country = form['country'].value()
return models.administrative_level_4.objects.filter(adm_level_5=country)
def land_initial(form):
country = form['country'].value()
return models.administrative_level_4.objects.filter(adm_level_5=country).first()
def district_choices(form):
land = form['land'].value()
return models.administrative_level_3.objects.filter(adm_level_4=land)
def district_inital(form):
land = form['land'].value()
return models.administrative_level_3.objects.filter(adm_level_4=land).first()
def town_choices(form):
district = form['district'].value()
return models.administrative_level_2.objects.filter(adm_level_3=district)
def town_initial(form):
district = form['district'].value()
return models.administrative_level_2.objects.filter(adm_level_3=district).first()
def municipality_choices(form):
town = form['town'].value()
return models.local_administrative_unit.objects.filter(adm_level_2=town)
def municipality_initial(form):
town = form['town'].value()
return models.local_administrative_unit.objects.filter(adm_level_2=town).first()
country = forms.ModelChoiceField(
queryset=models.administrative_level_5.objects.all(), empty_label='Select a country...'
)
land = DynamicField(
forms.ModelChoiceField,
queryset=land_choices,
initial=land_initial
)
district = DynamicField(
forms.ModelChoiceField,
queryset=district_choices,
initial=district_inital
)
town = DynamicField(
forms.ModelChoiceField,
queryset=town_choices,
initial=town_initial
)
siteMunicipality = DynamicField(
forms.ModelChoiceField,
queryset=municipality_choices,
initial=municipality_initial
)
siteNotes = forms.CharField(
required=False,
widget=forms.Textarea
)
siteName = forms.CharField()
It uses some htmx to fill the cascading dropdowns which makes it load much faster. The view looks like this:
class NewSiteView(FormMixin, View):
form_class = forms.NewSiteForm
template_name = 'datamanager/newsiteCascade.html'
success_url = 'datamanager/newsiteCascade.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form':form})
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
pol = models.local_administrative_unit.objects.values('geom').filter(lau_id=form.cleaned_data['siteMunicipality'].lau_id)[0]['geom']
cent_point = pol.centroid
geom = cent_point.wkt
municipality = form.cleaned_data['siteMunicipality']
site_name = form.cleaned_data['siteName']
site_notes = form.cleaned_data['siteNotes']
new_site = models.site(site_name = site_name, site_notes=site_notes, municipality=municipality, geom=geom)
new_site.save()
if 'Save' in request.POST:
return HttpResponseRedirect(reverse_lazy('data_manager:all_sites'))
elif 'SaveAnother' in request.POST:
return HttpResponseRedirect(reverse_lazy('data_manager:new_site'))
###############################################################
## HTMX Queries
###############################################################
def lands(request):
form = forms.NewSiteForm(request.GET)
return HttpResponse(form['land'])
def districts(request):
form = forms.NewSiteForm(request.GET)
return HttpResponse(form['district'])
def towns(request):
form = forms.NewSiteForm(request.GET)
return HttpResponse(form['town'])
def siteMunicipalities(request):
form = forms.NewSiteForm(request.GET)
return HttpResponse(form['siteMunicipality'])
However at the moment I have to add literature to those sites from another form (just a modelform from the cit_site model shown above) after creating the site first. I want to have this all done in once so I can create a site, add the citations and save it all together. I also want to keep using the cascading dropdowns from the form above to avoid the loading problem.
As far as I understand by now I need to use some kind of formset which holds both forms. But everything I found so far was using modelforms which wouldn't be useful in my case (no custom form). Also, the data for the parent form seems to already need to exist when the formset is rendered.
So I need some help how to approach this problem (maybe its two problems in one?). Thx in advance.
I made a form and there I had a multiple-choice field called artists which I got from my database and while adding a song a user can select multiple artists and save the song.
The artists are a ManyToManyField in Django models.
models.py
class Artists(models.Model):
""" Fields for storing Artists Data """
artist_name = models.CharField(max_length = 50, blank = False)
dob = models.DateField()
bio = models.TextField(max_length = 150)
def __str__(self):
return self.artist_name
class Songs(models.Model):
""" Fields for storing song data """
song_name = models.CharField(max_length = 30, blank = False)
genre = models.CharField(max_length = 30, blank = False)
artist = models.ManyToManyField(Artists)
release_date = models.DateField()
forms.py
class Song_input(forms.Form):
queryset = Artists.objects.only('artist_name')
OPTIONS = []
for i in queryset:
s = []
s = [i, i]
OPTIONS.append(s)
artist_name = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,choices=OPTIONS)
song_name = forms.CharField()
genre = forms.CharField()
release_date = forms.DateField(widget=DateInput)
Now I want to get all the values selected from the form and save to my database. Here the artist_name may have multiple values.
I have tried using the add() and create() methods but can not figure out how to add all the data where one field (artist_name) having multiple data to my database.
I strongly advise to make use of a ModelForm [Django-doc]. Especially since you make use of ManyToManyFields, which are more cumbersome to save yourself.
# app/forms.py
from django import forms
class SongForm(forms.ModelForm):
class Meta:
model = Songs
fields = '__all__'
widgets = {
'artist': forms.CheckboxSelectMultiple,
'release_date': forms.DateInput
}
There is thus no need to specify the fields yourself, you can change the widgets by adding these to the widgets dictionary [Django-doc] of the Meta subclass.
In your view, you can then both render and sae objects with that form:
# app/views.py
from app.forms import SongForm
def add_song(request):
if request.method == 'POST':
form = SongForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('name-of-some-view')
else:
form = SongForm()
return render(request, 'some-template.html', {'form': form})
The form.save() will save the object in the database.
In the template, you can then render the template:
<form method="post" action="{% url 'name-of-add_song-view' %}">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
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
I'm trying to display a form (ModelForm) with a select field filtered by currently logged in user. The select field in this case contains a list of categories. I want to display only the categories which "belong" to the currently logged in user. The category field is a foreign key to the IngredienceCategory model.
Here is what I've come up with so far but it's giving me an error (unexpected keyword queryset). Any ideas what I'm doing wrong?
# models.py
class IngredienceCategory(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
class Ingredience(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(IngredienceCategory, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredients"
def __unicode__(self):
return self.name
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
# views.py
def home(request):
if request.user.is_authenticated():
username = request.user.username
email = request.user.email
foods = Food.objects.filter(user=request.user).order_by('name')
ingredients = Ingredience.objects.filter(user=request.user).order_by('name')
ingrcat = IngredienceCategory.objects.filter(user=request.user)
if request.method == 'POST':
form = IngredienceForm(request.POST)
if form.is_valid():
# Create an instance of Ingredience without saving to the database
ingredience = form.save(commit=False)
ingredience.user = request.user
ingredience.save()
else:
# How to display form with 'category' select list filtered by current user?
form = IngredienceForm(queryset=IngredienceCategory.objects.filter(user=request.user))
context = {}
for i in ingredients:
context[i.category.name.lower()] = context.get(i.category.name.lower(), []) + [i]
context2 = {'username': username, 'email': email, 'foods': foods, 'ingrcat': ingrcat, 'form': form,}
context = dict(context.items() + context2.items())
else:
context = {}
return render_to_response('home.html', context, context_instance=RequestContext(request))
That's happening because ModelForm does not take a queryset keyword.
You can probably achieve this by setting the queryset on the view:
form = IngredienceForm()
form.fields["category"].queryset =
IngredienceCategory.objects.filter(user=request.user)
See related question here.
Here i have another suggestion to solve the problem. You can pass request object in your form object inside view.
In view.py just pass the request object.
form = IngredienceForm(request)
In your forms.py __init__ function also add request object
from models import IngredienceCategory as IC
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
def __init__(self, request, *args, **kwargs):
super(IngredienceForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = IC.objects.filter(user=request.user)
This filter always will be applied whenever you initialize your form .
I'm trying to display a form (ModelForm) with a select field filtered by currently logged in user. The select field in this case contains a list of categories. I want to display only the categories which "belong" to the currently logged in user. The category field is a foreign key to the IngredienceCategory model.
Here is what I've come up with so far but it's giving me an error (unexpected keyword queryset). Any ideas what I'm doing wrong?
# models.py
class IngredienceCategory(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
class Ingredience(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(IngredienceCategory, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredients"
def __unicode__(self):
return self.name
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
# views.py
def home(request):
if request.user.is_authenticated():
username = request.user.username
email = request.user.email
foods = Food.objects.filter(user=request.user).order_by('name')
ingredients = Ingredience.objects.filter(user=request.user).order_by('name')
ingrcat = IngredienceCategory.objects.filter(user=request.user)
if request.method == 'POST':
form = IngredienceForm(request.POST)
if form.is_valid():
# Create an instance of Ingredience without saving to the database
ingredience = form.save(commit=False)
ingredience.user = request.user
ingredience.save()
else:
# How to display form with 'category' select list filtered by current user?
form = IngredienceForm(queryset=IngredienceCategory.objects.filter(user=request.user))
context = {}
for i in ingredients:
context[i.category.name.lower()] = context.get(i.category.name.lower(), []) + [i]
context2 = {'username': username, 'email': email, 'foods': foods, 'ingrcat': ingrcat, 'form': form,}
context = dict(context.items() + context2.items())
else:
context = {}
return render_to_response('home.html', context, context_instance=RequestContext(request))
That's happening because ModelForm does not take a queryset keyword.
You can probably achieve this by setting the queryset on the view:
form = IngredienceForm()
form.fields["category"].queryset =
IngredienceCategory.objects.filter(user=request.user)
See related question here.
Here i have another suggestion to solve the problem. You can pass request object in your form object inside view.
In view.py just pass the request object.
form = IngredienceForm(request)
In your forms.py __init__ function also add request object
from models import IngredienceCategory as IC
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
def __init__(self, request, *args, **kwargs):
super(IngredienceForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = IC.objects.filter(user=request.user)
This filter always will be applied whenever you initialize your form .