I'm using Django to render a list of tube numbers with their attached ingredient. When clicking on a tube number it renders the list of all the ingredients. Then if I click on an ingredient I want it to replace the saved ingredient for this tube.
But I don't know how to proceed, if I should do a sub-app for ingredients in my tube app, or if their is a way to keep the tube id.
I tried many things, here is my actual views :
def tube_view(request):
tubes = Tube.objects.all()
return render(request, 'tube_ing.html', {'tubes': tubes})
def tube_detail_view(request, my_id):
obj = get_object_or_404(Tube, id=my_id)
request.session['my_id'] = my_id
ings = Ing.objects.all()
return render(request, "tube_detail.html", {"obj": obj, "ings": ings)
def tube_choice(request, id_ing):
obj = get_object_or_404(Ing, id=id_ing)
request.session['id_ing'] = id_ing
data = {'qty': 100}
form = TubeForm(data)
return render(request, "tube_choice.html", {"obj": obj, 'form': form})
def tube_form(request):
if request.method == 'POST':
form = TubeForm(request.POST)
if form.is_valid():
id_ing = request.session['id_ing']
form.cleaned_data['name_ing'] = my_id
t = Tube(name_ing_tube=name_ing_ing)
t.save()
else:
return HttpResponseRedirect('/tube_ing/')
else:
return HttpResponseRedirect('/tube_ing/')
My models :
class Tube(models.Model):
tube_number = models.IntegerField()
name_ing = models.CharField(null=False, blank=True, max_length=255, default='')
qty = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse('tube_ing:tube_detail', kwargs={"my_id": self.id})
class Ing(models.Model):
name_ing = models.CharField(null=False, blank=True, max_length=255, default='')
def get_absolute_url(self):
return reverse('tube_ing:tube_choice', kwargs={"id_ing": self.id})
And my url patterns :
path('', views.tube_view, name='tube_view'),
path('<int:my_id>/', views.tube_detail_view, name='tube_detail'),
path('ing/<int:id_ing>/', views.tube_choice, name='tube_choice'),
path('ing/tube_form/', views.tube_form, name='tube_form')
So I don't know what to put in my form and how to proceed to keep the tube's info.
Thanks a lot to the ones who will take some time to help me on this.
if [there's] a way to keep the tube id[?]
Yes, in your tube_form function try accessing request.session['my_id'], the tube ID was established from the the tube_detail_view.
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 have a model that holds an inventory value. I want to be able to add any integer value to that model field. It doesn't throw any errors but I get unexpected results when I post. For some reason I do not understand, the code is multiplying the number entered in the form by 2 and setting the result as the new inventory number. I have tried using both forms.Form and ModelForm for my form. The form I am using is also used to update the Panel description, so the text area for the description is pre-populated with the current Panel description. I'm not sure if it's important but the form input for the inventory is also pre-populated with the current inventory number. I didn't think this was a big deal as long as you delete it and type the actual amount of inventory you wanted to add.
For example, if I have an inventory of 80 and I enter 3 in the input field and POST, the new inventory will be 6. That is obviously not what I expect to happen, I would like it to POST and make the new inventory 83.
Models.py
class Panel(models.Model):
name = models.CharField('Name', max_length=50, default='')
slug = models.SlugField(unique=True)
description = models.TextField('Description', null=True, blank=True)
date_created = models.DateTimeField('Date Created', auto_now_add=True, null=True)
display = models.BooleanField(default=True)
image = models.ImageField('Panel Image', null=True, blank=True)
inventory = models.IntegerField('Inventory', null=True, default=0)
def get_absolute_url(self):
return reverse("model_detail", kwargs={"pk": self.pk})
def __str__(self):
return f'{self.name}'
def save(self, *args, **kwargs):
if not self.slug or self.slug != slugify(self.name):
self.slug = slugify(self.name)
return super().save(*args, **kwargs)
Forms.py
class UpdatePanelForm(ModelForm):
class Meta:
model = Panel
fields = ['description', 'inventory']
Views.py
def panel_edit(request, slug):
panel = Panel.objects.get(slug=slug)
form = UpdatePanelForm(instance=panel)
if request.method == 'POST':
form = UpdatePanelForm(request.POST, instance=panel)
if form.is_valid():
panel.description = form.cleaned_data.get('description')
current_inventory = panel.inventory
form_input = form.cleaned_data.get('inventory')
new_inventory = current_inventory + form_input
panel.inventory = new_inventory
panel.save()
return redirect('panels')
context = {'form': form, 'panel': panel}
return render(request, 'main/panel_edit.html', context)
I have also tried the below approach (probably the nicer way of writing it) and it also multiplies the number by 2.
add_number = form.cleaned_data.get('inventory')
panel.inventory += add_number
html
<form method='POST'>
{% csrf_token %}
<h3>{{panel.name}}</h3><br>
<h4>Panel Description</h4>
{{form.description}}<br>
<h4>Panel Inventory</h4>
<h4>Current Inventory: </h4><h2>{{panel.inventory}}</h2><br>
<h4>Add inventory below</h4><br>
{{form.inventory}}<br><br>
<input type="submit" value="Submit">
</form>
Found the answer by debugging my own code. Turns out I need to initialize current_inventory above the if statements. I'll just use JS to change the initial value of the input field.
def panel_edit(request, slug):
panel = Panel.objects.get(slug=slug)
current_inventory = panel.inventory
form = UpdatePanelForm(instance=panel)
if request.method == 'POST':
form = UpdatePanelForm(request.POST, instance=panel)
if form.is_valid():
panel.description = form.cleaned_data.get('description')
form_input = form.cleaned_data.get('inventory')
new_inventory = current_inventory + form_input
panel.inventory = new_inventory
panel.save()
return redirect('panels')
context = {'form': form, 'panel': panel}
return render(request, 'main/panel_edit.html', context)
I have several forms that take people through steps and below are the first two and the simplest ones and makes it easy to explain what i am having problem with.
The following two views are login required and contain one form on each. First view is the new_operator where the user fills out a single text input field. Second view is the new_asset where the user fills one text input field as the asset name and selects an operator from the a select/dropdown field. The question is how can i get the form to remember the operator name the user created in the previous form and make it as the default option? To be clear, i still want the user to select any other operator if they choose to do so but i want the option they just created to be the default. Thanks a lot in advance for the help.
First, here are the models:
class OperatorCompany(models.Model):
name = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='operator_added_by', null=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = "Operator Company"
verbose_name_plural = "Operator Companies"
def __str__(self):
return self.name
class AssetName(models.Model):
name = models.CharField(max_length=50, unique=True)
operator = models.ForeignKey(OperatorCompany, related_name='asset', on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='asset_added_by', null=True,
on_delete=models.SET_NULL)
class Meta:
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
views.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset')
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
and following are the forms.py without the init, clean functions and the widgets
class NewOperatorForm(forms.ModelForm):
class Meta:
model = OperatorCompany
fields = ('name',)
class NewAssetForm(forms.ModelForm):
class Meta:
model = AssetName
fields = ('name', 'operator')
To share data between multiple pages, you can use session variables. These are stored on the server and associated to clients according to the session cookie they communicate to the server at every request.
Typically, in the first view, you would add after save():
request.session['latest_created_operator_id'] = newoperator.id
to save in the session the operator id.
And in the second view, after the else,
operator_id = request.session.get('latest_created_operator_id', None)
operator = Operator.objects.filter(id=operator_id).first() # returns None if not found
form = NewAssetForm(initial={'operator': operator})
retrieves the operator and populates the form.
(That's untested code; you may need to edit a bit.)
At a glance, maybe something like this would work.
What you can do is add another URL in urls.py for new_asset which accepts a OperatorCompany id. I don't have your url config but it could be something like:
urls.py
path('wellsurfer/new_asset/<int:operator_id>', new_asset, name='wellsurfer:new_asset_operator')
view.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset', operator_id=newoperator.id)
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request, operator_id=None):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
if operator_id is not None:
operator_company = OperatorCompany.objects.get(pk=operator_id)
form.fields['operator'].initial = operator_company
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
This is a very beginner-orientated question but I've been stuck on this issue all day.
I would like to load the data for a specific record and be able to save it (Submit button in a template) but i'm still trying to understand instances and the save method.
models.py
class model_essays(models.Model):
user = models.ForeignKey(User, default='1', on_delete=models.CASCADE)
title = models.CharField(max_length=100, default='')
date_added = models.models.DateTimeField(auto_now_add=True)
body = models.TextField()
def __str__(self):
return self.title
I understand the id is created automatically
forms.py
class frm_essays (forms.ModelForm):
class Meta:
model = model_essays
fields = ['title', 'date_added', 'body']
urls.py
urlpatterns = [
path('essay/<int:pk>', views.views_essay),
]
views.py {stuck here}
#login_required
def views_essay(request, pk):
if request.method == 'POST':
updatedForm = essay_detail(request.POST, instance=request.? {I want the ID of the essay})
if u_form.is_valid():
updatedForm.save()
messages.success(request, f'this essay has been updated')
return redirect('essay_detail')
else:
updatedForm = frm_essays(instance=request.{as above})
context = {
'updatedForm': updatedForm
}
return render(request, 'essay_detail.html', context)
On the decorator - I haven't gotten around to only allowing the users to view their own created essays, this would be the next large hurdle but not the issue I'm asking about.
Unless I am mistaken you are looking for the same ID as the pk (primary key). You have that passed in as an argument to your function.
You just need to query the instance from the DB.
def views_essay(request, pk):
essay = model_essays.objects.get(pk=pk)
if request.method == 'POST':
updatedForm = essay_detail(request.POST, instance=essay)
...
Note: essay will be None if the query based on the pk does not find an match in the DB.
I have a "product" field that I want to use to determine which form to display. I am trying to do this in the view but wondering if I should do it in the template instead. I have tried the following but "form" does not get assigned by my if statements. What am I doing wrong?
#login_required
def update_message(request, pk):
message = get_object_or_404(Submission, pk=pk)
author = message.author
date_posted = message.date_posted
product = message.product
message_obj = Submission.objects.get(pk=pk)
program_type = message.program_type
if author == request.user:
if request.method == 'POST':
if product == 'Apple':
form = AppleForm(request.user, request.POST, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, request.POST, instance=message)
if form.is_valid():
message_sub = form.save(commit=False)
message_sub.author = request.user
message_sub.date_posted = timezone.now()
message_sub.save()
form.save_m2m()
messages.success(request, 'Message updated')
return redirect('submission-list')
else:
if product == 'Apple':
form = AppleForm(request.user, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, instance=message)
else:
messages.warning(request, 'You can't do that.')
return redirect('message-submission-list')
return render(request, 'programs/submission_create_form.html', {'product':product,'form': form, 'message_obj': message_obj,'program_type':program_type})
class MessageSubmission(models.Model):
message = models.CharField(max_length=5000)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
program_code = models.ManyToManyField(Program)
program_type = models.CharField(max_length=30, blank=True)
product = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)
production_cycle = models.ManyToManyField('ProductionCycle', null=True)
def get_absolute_url(self):
return reverse('submission-list')
def __str__(self):
return self.message
As I mentioned in the comment, the issue is that product is a ForeignKey to another model. In the template, the FK will display using the __str__ method of that model, but that doesn't make it equal to that display value. You should compare explicitly with the relevant field on the target model:
if product.fruit_type == 'Orange' # or whatever the field is
(Alternatively you could do if str(product) == 'Orange' but that's more brittle and is coupling display logic in a way that's not very nice.)
There's nothing wrong with doing this in the views. If the form is not defined after those if statements then it means that the value of product is not Apple or Orange, but something else. I would double check the value of product to fix the issue.
Since Product is a class, you should reference a field. You didn't post the code for it, but for example
if form == product.name
If there is a name field.