I have 3 models:
class Node(models.Model):
ID = models.DecimalField(max_digits=19, decimal_places=10)
name = models.CharField(default='node', max_length=32)
connexion = models.CharField(max_length=255)
# Many2one fields | Foreign keys:
firm = models.ForeignKey('firme.Firme', on_delete=models.CASCADE, null=True, blank=True)
class ScheduledAction(models.Model):
date = models.DateTimeField(default=datetime.now, blank=True)
firm = models.ForeignKey('firme.Firme', on_delete=models.CASCADE, null=True, blank=True)
node_ids = models.ManyToManyField(Node)
I want in ScheduledAction form to show, for a selected firm, the list of its related nodes. Normally I should do this by get:
class ScheduledActionForm(forms.ModelForm):
date = forms.DateTimeField()
firm = forms.ModelChoiceField(queryset=Firme.objects.all())
node_ids = forms.ModelMultipleChoiceField(queryset=Node.objects.get(firm_id=firm.id), widget=forms.CheckboxSelectMultiple)
class Meta:
model = ScheduledAction
fields = [
'date',
'firm',
'node_ids'
]
This is my views.py:
def planification_view(request, id):
scheduledAction = ScheduledActionForm(request.POST or None)
firme = get_object_or_404(Firme, id=id)
nodes = Node.objects.all()
if scheduledAction.is_valid():
scheduledAction.save()
print('formulaire enregistre')
scheduledAction = ScheduledActionForm()
context = {
'firme': firme,
'form': scheduledAction,
'nodes': nodes
}
return render(request, "node/planification.html", context)
But I got this error:
AttributeError: 'ModelChoiceField' object has no attribute 'id'
How can I fix this?
There are several things wrong with your approach
first of all: you assigned the variable 'firm' to a form field and then using that same variable to get an object from the db won't work in anyway. The variable 'firm' in Node.objects.get(firm_id=firm.id) should be a firm instance.
second: queryset=Node.objects.get(firm_id=firm.id) won't work because Model.objects.get() will return an object not a query set.
If you want to show all the nodes for a specific Firm you will need to pass the firm object (or id or other identifier) to the form by for example passing it through the get_form_kwargs() if you are using CBV and filtering by it Node.objects.filter(firm_id=firm.id)
Update:
Since you are using function based views there might be a more dry way to do this, but I'm only used to working with CBV so I'll try my best
On the form we will declare a new variable called firme (we will pass this to the form in your view)
class ScheduledActionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.firme = kwargs.pop('firme')
super(ScheduledActionForm, self).__init__(*args, **kwargs)
self.fields['node_ids'].queryset = Node.objects.filter(firm_id=self.firme.id)
date = forms.DateTimeField()
firm = forms.ModelChoiceField(queryset=Firme.objects.all()) # why do you want a list of all firms here? Or do you want a single object?
node_ids = forms.ModelMultipleChoiceField(queryset=Node.objects.none()), widget=forms.CheckboxSelectMultiple)
class Meta:
model = ScheduledAction
fields = [
'date',
'firm',
'node_ids'
]
now for you view:
def planification_view(request, id):
firme = get_object_or_404(Firme, id=id)
nodes = Node.objects.all()
if request.method == 'POST':
form = ScheduledActionForm(data=request.POST, firme=firme)
if form.is_valid():
scheduledAction = form.save()
print('formulaire enregistre')
else:
form = ScheduledActionForm(firme=firme)
context = {
'firme': firme,
'form': form,
'nodes': nodes
}
return render(request, "node/planification.html", context)
Something along these lines should get you going. It might still require some tweaking to get it to work in your project. If you still have trouble understanding please read the Django docs and or do their tutorial
Update 2:
Not the prettiest solution but it works for the OP
Related
I have this postgres function, What it does is when I save an item, it creates an cl_itemid test2021-20221-1. then in table clearing_office the column office_serial increments +1 so next item saved will be test2021-20221-2
SELECT CONCAT(f_office_id,f_sy,f_sem,'-',office_serial) INTO office_lastnumber from curriculum.clearing_office
where office_id=f_office_id;
UPDATE curriculum.clearing_office SET office_serial =office_serial+1 where office_id=f_office_id;
{
"studid": "4321-4321",
"office": "test",
"sem": "1",
"sy": "2021-2022",
}
Is it possible to create this through Django models or perhaps maybe an alternative solution for this problem?
This is my model class
class Item(models.Model):
cl_itemid = models.CharField(primary_key=True, max_length=20)
studid = models.CharField(max_length=9, blank=True, null=True)
office = models.ForeignKey('ClearingOffice', models.DO_NOTHING, blank=True, null=True)
sem = models.CharField(max_length=1, blank=True, null=True)
sy = models.CharField(max_length=9, blank=True, null=True)
class Meta:
managed = False
db_table = 'item'
One simple way might be to use a default function in python.
def get_default_id():
last_id = Item.objects.last().cl_itemid
split_id = last_id.split('-')
split_id[-1] = str(int(split_id[-1])+1)
new_id = '-'.join(split_id)
return new_id
class Item(models.Model):
cl_itemid = models.CharField(..., default=get_default_id)
Similar approach described in this answer.
One thing you will need to anticipate with this approach is that there might be a race condition when two or more new objects are created simultaneously (i.e., the default function runs at the same time for two new objects, potentially resulting in the same ID being returned for the two new objects). You can work around that potential issue with transactions or retries if you think it will be a problem in your application.
A robust approach would be to create your own model field and handle the implementation internally in the field. That would allow you to implement the solution in different ways, depending on the DB dialect and have it work across DB implementations without the race condition issue.
I do this in alot of my Models, tad different. I just use Forms and tac on a custom save method
I haven't worked with unmanaged models, but from what I can see from google it's not different
Form (forms.py)
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = (
'cl_itemid',
'studid',
'office',
'sem',
'sy',
)
def __init__(self, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(ItemForm, self).save(commit=False)
if not self.instance:
# you only want it hitting this if it's new!
from datetime import datetime
now = datetime.now()
count = Item.objects.all().count()
# you could also just grab the last
# + chop the id to get the last number
obj.cl_itemid = 'test{0}-{1}'.format(now.strftime('%Y-%m%d%y'), count)
if commit:
obj.save()
You might even be able to tac it BEFORE the actual save method super(ItemForm, self).save(commit=False) or even do a cl_itemid ..i'll do some testing on my end to see if that could be an option, might be much cleaner
Usage
View (POST)
def ItemFormView(request):
from app.forms import ItemForm
if request.method == "POST":
# this is just for edits
itemObj = Item.objects.get(pk=request.POST.get('itemid')) if request.POST.get('itemid') else None
form = ItemForm(request.POST or None, instance=itemObj)
if form.is_valid():
obj = form.save()
return HttpResponseRedirect(reverse('sucesspage')
print(form.errors)
print(form.errors.as_data())
# this will render the page with the errors
data = {'form': form}
else:
data = {'form': ItemForm()}
return render(request, 'itemform.html', data)
Shell
f = ItemForm(data={'office':'test', 'sem':'1', 'etc':'etc'})
if f.is_valid():
o = f.save()
I have a model "TnaTemplateModel"
class TnaTemplateModel(models.Model):
id = models.UUIDField(primary_key = True,default=uuid.uuid4, editable=False)
template_name = models.ForeignKey(TemplateNameModel, verbose_name="template name", null=False,
blank=False, on_delete=models.CASCADE, help_text="Select the template")
process_name = models.ForeignKey(ProcessModel, verbose_name="process name", null=False,
blank=False, on_delete=models.CASCADE, help_text="Select the process")
sequence = models.IntegerField(verbose_name="Process Sequence",null = False,blank = False)
is_base = models.BooleanField()
dependent_process = models.CharField(verbose_name="Dependent Process",null=True, blank= True,max_length= 150)
formula = models.IntegerField(verbose_name="Formula", null= True,blank = True)
remarks = models.CharField(verbose_name="Process remarks", null= True, blank = True,max_length= 300)
class Meta:
unique_together = ["template_name", "process_name"]
def __str__(self):
return str(self.template_name)
I need to save entries from a form where dependent_process field will be a list of all processes in TNATemplateModel with the desired template_name and where is_base = True.
For this I have created 2 views and 2 forms 1 each for first saving all the entries with is_base = True and second for saving entries which will have a dependent_process as a dropdown from the previously added is_base = true processes. Note that the dropdown list should show processes with the same template_name where is_base = True.
Views.py
#for saving processes with is_base = true
def baseprocesscreatenew(request,pk):
template_name = TemplateNameModel.objects.get(id=pk)
data = {'template_name': template_name.id,'is_base': True}
dependent_list = TnaTemplateModel.objects.filter(template_name = template_name.id)
if request.method == 'POST':
form = BaseProcessModelformNew(request.POST,initial= data)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('templatepointslist_new',kwargs={'pk':pk}))
else:
form = BaseProcessModelformNew(initial= data)
print(form.errors)
return render (request,"tna/template_tna/baseprocessmodel_create.html",{'form':form,'dependent_list':dependent_list})
#for saving dependent processes
def dependentprocesscreatenew(request,pk):
template_name = TemplateNameModel.objects.get(id=pk)
data = {'template_name': template_name.id,'is_base': False}
dependent_list = TnaTemplateModel.objects.filter(template_name = template_name.id)
if request.method == 'POST':
form = DependentProcessModelformNew(request.POST,initial= data)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('templatepointslist_new',kwargs={'pk':pk}))
else:
form = DependentProcessModelformNew(initial= data)
print(form.errors)
return render (request,"tna/template_tna/dependentprocessmodelnew_create.html",{'form':form,'dependent_list':dependent_list})
** forms.py **
#form for saving base process
class BaseProcessModelformNew(forms.ModelForm):
class Meta:
model = TnaTemplateModel
fields =('__all__')
widgets = {'template_name': forms.HiddenInput(),'dependent_process:':forms.HiddenInput(),
'formula':forms.HiddenInput(),'is_base':forms.HiddenInput()}
#for Saving Dependent process
class DependentProcessModelformNew(forms.ModelForm):
class Meta():
model = TnaTemplateModel
fields =('__all__')
widgets = {'template_name': forms.HiddenInput(),
'is_base':forms.HiddenInput()}
def __init__(self,*args,**kwargs):
template_id= kwargs['initial']['template_name']
tna_template_name = TemplateNameModel.objects.get(id= template_id)
super (DependentProcessModelformNew,self ).__init__(*args,**kwargs)
dependent_obj_list = TnaTemplateModel.objects.filter(template_name = tna_template_name,is_base=True)
print("*************",dependent_obj_list)
def convert_queryset_tuple(obj):
dependent_list=[]
for a in obj:
dependent_list.append[a.process_name]
return tuple(dependent_list)
dependent_list = convert_queryset_tuple(dependent_obj_list)
self.fields['dependent_process'] = forms.ChoiceField(choices=dependent_list)
I have tried multiple approaches to this problem. I tried making a separate model for base and dependent process but since I have to calculate dates for dependent process which will depend on the dates entered in the base process , I wasn't able to figure out how can i do that with 2 different model.
After making one model for the base process I am unable to figure out :
How can I pass dependent list to html template and create the dropdown as I am using django form
If I am using ChoiceField I am not able to get the dynamic tuple out of the queryset and i believe ChoiceField cannot be create without a tuple
I have spent a lot of time trying to find the best way to achieve and I seeking to find the best approach to do this.
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 am trying to create a form where one field is a ModelChoicefield. Im trying to populate that field with objects from a different model. I have ran into a problem as i need to get the current logged user within the form to filter the queryset. Here are the 2 models
class UserExercises(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
Muscle = models.ForeignKey(Muscle, on_delete=models.CASCADE)
class Exercise(models.Model):
exercise = models.ForeignKey(UserExercises, on_delete=models.CASCADE)
weight = models.DecimalField(max_digits=6, decimal_places=3)
reps = models.PositiveIntegerField(validators=[MaxValueValidator(100)])
difficulty = models.CharField(max_length=30)
date = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE)
And here is my form
class AddExerciseForm(forms.Form):
exercise = forms.ModelChoiceField(queryset=UserExercises.objects.filter(user=1))
class Meta:
model = Exercise
fields = ['exercise', 'weight', 'reps', 'difficulty']
As you can see i am currently hard coding a filter in the ModelChoiceField, but want to replace that with the current users Id. Is there anyway of Going about this. Im new to django so any help would be Appreciated.
My View
#login_required
def add_exercise_view(request):
if request.method == 'POST':
user_id = request.user.id
form = AddExerciseForm(user_id=user_id)
if form.is_valid():
form.save()
return redirect('myfit-home')
else:
form = AddExerciseForm()
return render(request, 'users/register.html', {'form': form})
Firstly, AddExerciseForm should extend forms.ModelForm.
To initialize form data based on some paramater, you can override __init_ method of ModelForm to update form fields (that field is exercise in this case) based on some argument/parameter (which is user_id in this case).
class AddExerciseForm(forms.ModelForm):
class Meta:
model = Exercise
fields = ['exercise', 'weight', 'reps', 'difficulty']
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user_id', None)
super(AddExerciseForm, self).__init__(*args, **kwargs)
if user_id is not None:
# update queryset for exercise field
self.fields['exercise'].queryset = UserExercises.objects.filter(user=user_id)
else:
# UserExercises.objects.none() will return an empty queryset
self.fields['exercise'].queryset = UserExercises.objects.none()
And pass the user_id while initializing the form in view:
if request.user.is_authenticated():
# get user id
user_id = request.user
form = AddExerciseForm(user_id=user_id)
override __init__ method of the Form, and pass the user as argument
def __init__(self,user,*args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
self.fields['exercise'].queryset=
UserExercises.objects.filter(user=self.user))
self.fields['exercise'].widget = forms.CheckboxSelectMultiple
class Meta:
model = Exercise
fields = ['exercise', 'weight', 'reps', 'difficulty']
I've got the following Form. I let Django render the form automatically for me in my template with: {{ form.as_p() }}. As you can see I've got the company field, however, it's redundant as I am setting the company by clean_company. The company field is hidden, but I want it to be completely gone in the template. I still need it in the form though, because I want to be able to call: form.save(commit=True).
Is there a way to get the hidden field out of my template?
class PlantPurchaseForm(forms.ModelForm):
company = forms.CharField(initial="", widget=forms.HiddenInput())
number = forms.IntegerField(initial=10000, min_value=10000, max_value=99999)
date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
class Meta:
model = PlantPurchase
fields = (
"company",
"number",
"date",
"plant",
"costs",
)
def __init__(self, company, *args, **kwargs):
self.company = company
super(PlantPurchaseForm, self).__init__(*args, **kwargs)
def clean_company(self):
data = self.cleaned_data["company"]
data = self.company
return data
def clean_date(self):
data = self.cleaned_data["date"]
data = datetime.combine(data, time(hour=12))
data = pytz.utc.localize(data)
return data
The PlantPurchase Model:
class PlantPurchase(models.Model):
company = models.ForeignKey(Company, related_name="plant_purchases")
number = models.PositiveSmallIntegerField(unique=True, validators=[MinValueValidator(10000),
MaxValueValidator(99999)])
date = models.DateTimeField()
plant = models.ForeignKey(Plant, related_name="plant_purchase", on_delete=models.PROTECT)
costs = models.DecimalField(max_digits=8, decimal_places=2)
class Meta:
unique_together = ("company", "number")
def __str__(self):
text = "Purchase: #{} {}".format(self.number, self.plant)
return text
I solved my problem by accessing the form instance in the init method. This way I could remove the company field and it won't be rendered in the template anymore.
class PlantPurchaseForm(forms.ModelForm):
number = forms.IntegerField(initial=10000, min_value=10000, max_value=99999)
date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
class Meta:
model = PlantPurchase
fields = (
"number",
"date",
"plant",
"costs",
)
def __init__(self, company, *args, **kwargs):
super(PlantPurchaseForm, self).__init__(*args, **kwargs)
self.instance.company = company
def clean_date(self):
data = self.cleaned_data["date"]
data = datetime.combine(data, time(hour=12))
data = pytz.utc.localize(data)
return data
There are several ways how to do it.
Basically, if you are sure that hidden field is not in use then you can remove it from the form with exlude. You can remove init method as well.
class PlantPurchaseForm(forms.ModelForm):
number = forms.IntegerField(initial=10000, min_value=10000, max_value=99999)
date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
class Meta:
model = PlantPurchase
fields = [
'company',
'number',
'date',
'plant',
'costs',
]
exclude = ["company"]
After that you need to save company into the models instance. You can either save it after form is validated:
def post(self, request):
...
if form.is_valid():
plantPurchase = form.save()
plantPurchase.company = company
plantPurchase.save()
...
..or pass the company into the save method of the form:
def save(self, company, force_insert=False, force_update=False, commit=False):
purchase = super(PlantPurchaseForm, self).save(commit=commit)
purchase.company = company
purchase.save()
return purchase