Django ManyToMany Validation and Save - python

I am developing a Leave of Absence Request Form for a client. The form which is filled out by an employee allows the employee to select mutiple days which he/she wants to take off. When the employee initially views the form only one date entry is shown. They can add more dates dynamically (javascript) by click an add button.
Database setup: Form_Table, Date_Table, Form_Date_ManyToMany Table (generated by Django)
I have 2 issues with Django.
1) If the employee enters 3 dates, each date field will have the same name. How can I validate each field using Django's Form or ModelForm? Here is an example of the the date field.
<input name="dates[]" />
<!-- or -->
<input name="dates" /> <!-- I have read this is the Django way -->
2) In my database I have a ManyToMany relationship with the Form and the Dates. I am sure I need ManyToMany and not simply a ForeignKey for tracking changes (this is not the question). How can I save multiple dates (in the dates table) and have the ManyToMany relationship with the form (in the forms table) using Django.?
I am relatively new to Python & Django. I switched from PHP since Django is the best framework I have found.
Edit: I guess I did not ask the correct question. Here is more information.
# Model
class Date(models.Model):
date = models.DateField()
emp_requested = models.ForeignKey(EmployeeRequested,null=True)
hr_approved = models.ForeignKey(HumanResourcesApproved,null=True)
class Form(models.Model):
pay_period = models.ForeignKey(PayPeriod)
employee = models.ForeignKey(Employee)
ack_ip = models.CharField(max_length=15, default='')
emp_signature = models.CharField(max_length=100)
date_of_hire = models.DateField()
state = models.ForeignKey(State)
scheduler = models.ForeignKey(Scheduler)
scheduler_form = models.ManyToManyField(SchedulerForm)
hr_status = models.CharField(max_length=100, default='')
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
enabled = models.BooleanField(default=True)
parent = models.ForeignKey('self',related_name='+')
root = models.ForeignKey('self',related_name='+')
dates = models.ManyToManyField(Date)
# View - this is an ajax call
def ajax_save_form(request):
form = EmployeeForm(request.POST or None)
return HttpResponse(form.is_valid())
# Form
class EmployeeForm(forms.ModelForm):
employee_name = forms.CharField(max_length=30)
employee_email = forms.EmailField()
county = forms.ModelChoiceField(queryset=County.objects.all())
job_type = forms.ModelChoiceField(queryset=JobType.objects.all())
pay_period = forms.CharField()
total_hours = forms.DecimalField()
class Meta:
model = Form
exclude = ( 'employee', 'ack_ip', 'state', 'scheduler', 'scheduler_form', 'hr_status', 'parent', 'root', 'dates',)
# HTML (template)
<!-- Stuff -->
<div id="date-group" class="control-group date-controls">
<label class="control-label" for="date">Date</label>
<div class="controls">
<div class="input-append">
<input type="text" placeholder="Click to see Calendar" name="dates[]" class="input-xlarge emp-dates" id="date">
<span class="add-on"><i class="icon-calendar"></i></span>
<a id="AddDateBtn" class="btn btn-primary"><i class="icon-plus icon-white"></i> Add additional day</a>
<br><br>
</div>
<div class="pull-left sub-controls">
<h4>Employee Section</h4>
<div class="well">
Reason for Absence:<br>
<select name="reasons[]" class="emp-reasons">
{% for reason in reasons %}
<option value="{{ reason.id }}">{{ reason.reason }}</option>
{% endfor %}
</select>
<span class="add-on emp-reason-help"><i class="icon-question-sign"></i></span>
<br><br>
Hours: <br>
<select name="hours[]">
{% for hour in hours %}
<option value="{{ hour }}">{{ hour }}</option>
{% endfor %}
</select>
<br><br>
Explanation Required:<br>
<textarea class="input-xlarge" name="explanations[]"></textarea>
</div>
</div>
</div>
</div>
<!-- Stuff -->
The Employee can have as many of the HTML blocks as they want. Using JavaScript I dynamically "copy" the html that is shown above. This allows the employee to submit multiple dates (dates[], hours[], reasons[], explanations[]). I need to save each date group (dates[i], hours[i], reasons[i], explanations[i]) in the Date Model. Then I need to have a ManyToMany relationship with the Form Model and all the dates that were submitted. How can I do this using ModelForm.
I have seen some code online that suggests that it is possible to do this:
form = EmployeeForm()
if form.is_valid():
# do relationships here
I am also at a loss of how to get the values form dates[], hours[], reasons[], and explanations[] using Django.

If you really need to store these dates in separate table with ManyToMany relationship, then you should extend your ModelForm a little bit.
First, specify the class for your dates field, which should inherit from django.forms.ModelMultipleChoiceField. In it, override the clean function, and create all the Date objects there. The function should return the list of ids of created objects (or consider returning the results of parent's clean function, passing it this list of ids). Then in your ModelForm you just specify that the field is of your new class, and then all the validation and saving will work.
Hope it will help; if I've been unclear, ask more:)

Related

How to create a form with fields for all instances of another model in Django?

I'm new to Django and I'm currently trying to create a form which should contain input fields for all existing objects of another model. Let's say that I would like to manage my supplies at home and make sure that I have enough of various products at diffect storage locations.
For example, I would like to make sure that the storage locations bathroom and basement should always have plenty of the supply toilet paper. I don't need toilet paper in the location kitchen. The storage locations are pre-defined and available through a model. I couldn't find any way to create a form for Supply which dynamically generates form fields for MinimumQuantity objects based on all available StorageLocation objects.
My form should look like this when creating the supply "toilet paper":
supply name: <input example: "toilet paper">
minimum supply quantities
bathroom - amount: <input example: 6>
kitchen - amount: <input example: 0>
basement - amount: <input example: 3>
I'm a bit lost here and any hints would be much appreciated.
This is my current code (shortened):
models.py:
class Supply(models.Model):
name = models.CharField(max_length=50)
class StorageLocation(models.Model):
name = models.CharField(max_length=50)
class MinimumQuantity(MinimumQuantity):
storage_location = models.ForeignKey(StorageLocation, on_delete=models.PROTECT)
supply = models.ForeignKey(Supply, on_delete=models.PROTECT)
amount = models.IntegerField()
views.py:
class SupplyCreateView(CreateView):
model = Supply
template_name = "supplies_form.html"
fields = [ 'name', ]
supplies_form.html:
<div class="card-body">
{% csrf_token %}
{{ form|crispy }}
</div>
I stumbled upon various related questions on stackoverflow and other sites and fiddled around with formsets, but my issues seem to be that:
I have a one-to-many from Supply to MinimumQuantity and can't think of any way to tell Supply about it, and
while all StorageLocation objects exist already, the MinimumQuantity objects don't.
Have I perhaps even made this construct too complicated and is there perhaps a way to solve the whole thing without the MinimumQuantity model at all? Thanks!
In this, we have to use a ModelForm and set the fields attribute to include the fields of the MinimumQuantity model that you want to include in the form. In your case, this would be the storage_location field and the amount field.
Now, First add this in your code and make changes to SupplyCreateView:
from django.forms import ModelForm
class MinimumQuantityForm(ModelForm):
class Meta:
model = MinimumQuantity
fields = ['storage_location', 'amount']
class SupplyCreateView(CreateView):
model = Supply
template_name = "supplies_form.html"
form_class = MinimumQuantityForm
fields = ['name']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['minimum_quantities'] = MinimumQuantity.objects.all()
return context
and then in your supplies_form.html do like this:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="card-body">
<h4>Minimum supply quantities</h4>
{% for minimum_quantity in minimum_quantities %}
<div class="form-group">
<label for="id_{{ minimum_quantity.storage_location.name }}">{{ minimum_quantity.storage_location.name }}</label>
<input type="number" name="{{ minimum_quantity.storage_location.name }}" id="id_{{ minimum_quantity.storage_location.name }}" required>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Save</button>
</div>

Django UpdateView: How to display the original values?

I'm using Django UpdateView to update a Model class
class TestCase(models.Model):
name = models.CharField(max_length=200)
executable = models.CharField(max_length=1023)
parameter_value_list = models.TextField()
test_type = models.CharField(max_length=200)
created_by = models.CharField(max_length=200, default = "user")
create_datetime = models.DateTimeField("testcase created on", auto_now = True)
My view is as:
class MyEditCaseView(UpdateView):
model = TestCase
fields = ['name', 'executable', 'parameter_value_list', 'test_type']
template_name_suffix = '_update_form'
def get_success_url(self):
return reverse("myApp:testCase")
My template is as:
<form action="{% url 'myApp:editCase' case.id %}" method="post">
{% csrf_token %}
<input type="submit" value="Edit">
</form>
urls:
path('editCase/<int:pk>/', views.MyEditCaseView.as_view(), name='editCase')
It works OK, but I have 3 questions:
The update view page pop out is blank for all fields even they have old values. I won't like to change all fields. How to show the old values, if I don't want update all fields?
The default lay out of the update view page seems not elegant. Can I change the style?
When clicking the input box, saying Name field, a drop down list will show to give some historic values I had used as candidates. Can I delete or change this historic list?
Django form can be spread out.
<!--begin::Form-->
<form method="post">
{% csrf_token %}
{% field in form.fields %}
<input name="{{ field.name }}" type="text" value="{{ field.value }}"/>
{% endfor %}
</form>
<!--end::Form-->
The update view page pop out is blank for all fields even they have old values. I won't like to change all fields. How to show the old values, if I don't want update all fields?
You can use default value as above example, value field.
The default lay out of the update view page seems not elegant. Can I change the style?
You can use class attribute in each input fields.
When clicking the input box, saying Name field, a drop down list will show to give some historic values I had used as candidates. Can I delete or change this historic list?
If you set autucomplete attribute as off, then any history would not appear.
<input type="text" autocomplete="off" />

Django inline formset will always create new object instead of update them

I've 2 model First and Second with a FK from Second to First. I created a form for the 2 class and a inline formset for Second. On template I manually designed my form and with jQuery I'm able to add dynamic forms of Second.
On UpdateView the form is correctly populated, but when I submit the form, all Second instances are created again with new ids instead of updating them. I double checked that on HTML there are name=PREFIX-FORM_COUNT-id with correct ids, but seems that Django ignores it.
I'm using Django 2.2.12 & Python 3.6
Here what I made:
models.py
class First(models.Model):
name = models.CharField(max_length=100, null=False)
class Second(models.Model):
first= models.ForeignKey(First, null=False, on_delete=models.CASCADE)
number= models.FloatField(null=False, default=0)
form.py
class FirstForm(forms.ModelForm):
class Meta:
model = First
fields = "__all__"
class SecondForm(forms.ModelForm):
class Meta:
model = Second
fields = "__all__"
SecondsFormset = inlineformset_factory(First, Second, SecondForm)
view.py
class FirstUpdateView(UpdateView):
template_name = "first.html"
model = First
form_class = FirstForm
context_object_name = "first_obj"
def get_success_url(self):
return reverse(...)
def forms_valid(self, first, seconds):
try:
first.save()
seconds.save()
messages.success(self.request, "OK!")
except DatabaseError as err:
print(err)
messages.error(self.request, "Ooops!")
return HttpResponseRedirect(self.get_success_url())
def post(self, request, *args, **kwargs):
first_form = FirstForm(request.POST, instance=self.get_object())
second_forms = SecondsFormset(request.POST, instance=self.get_object(), prefix="second")
if first_form .is_valid() and second_forms.is_valid():
return self.forms_valid(first_form , second_forms)
...
.html (putted only essential tags)
<form method="post">
{% csrf_token %}
<input type="text" id="name" value="{{ first_obj.name }}" name="name" required>
<input type="hidden" name="second-TOTAL_FORMS" value="0" id="second-TOTAL_FORMS">
<input type="hidden" name="second-INITIAL_FORMS" value="0" id="second-INITIAL_FORMS">
<input type="hidden" name="second-MIN_NUM_FORMS" value="0" id="second-MIN_NUM_FORMS">
<div id="seconds_container">
{% for s in first_obj.second_set.all %}
<input type="hidden" name="second-{{forloop.counter0}}-id" value="{{s.pk}}">
<input type="hidden" name="second-{{forloop.counter0}}-first" value="{{first_obj.pk}}">
<input type="number" min="0" max="10" step="1" value="{{s.number}}" name="second-{{forloop.counter0}}-number" required>
{% endfor %}
</div>
<button class="btn btn-success" type="submit">Update</button>
</form>
I checked how Django creates forms and it will only add DELETE checkbox on it, but all other infos are correctly stored into the formset. When I do .save() it will create new Second element on db instead of change them.
What am I missing?
I solved this!
I setted TOTAL_FORMS and INITIAL_FORMS with wrong values. From Django's docs:
total_form_count returns the total number of forms in this formset. initial_form_count returns the number of forms in the formset that were pre-filled, and is also used to determine how many forms are required. You will probably never need to override either of these methods, so please be sure you understand what they do before doing so.
So the correct way to use it is:
In views:
Generate FormSets with extra=0
In HTML:
Set TOTAL_FORMS with number of rows you are POSTing and change it dinamically if dinamically add/remove rows;
Set INITIAL_FORMSwith number of alredy filled rows (editing/deleting) and never change this;
To delete a pre-filled row use DELETE checkbox instead of removing entire row;
For me i wanted to update my images, everything suggested here and every other forums about handling the hidden form didn't worked until i changed this.
product_img_form = ProductImageFormSet(data=request.FILES or None, instance=your_model_instance)
To this.
product_img_form = ProductImageFormSet(request.POST or None, request.FILES or None, instance=your_model_instance)
Then like magic this ugly error stopped showing, and my new image successfully got updated
<tr><td colspan="2">
<ul class="errorlist nonfield">
<li>(Hidden field TOTAL_FORMS) This field is required.</li>
<li>(Hidden field INITIAL_FORMS) This field is required.</li>
</ul>
<input type="hidden" name="product_images-TOTAL_FORMS" id="id_product_images-TOTAL_FORMS">
<input type="hidden" name="product_images-INITIAL_FORMS" id="id_product_images-INITIAL_FORMS">
<input type="hidden" name="product_images-MIN_NUM_FORMS" id="id_product_images-MIN_NUM_FORMS">
<input type="hidden" name="product_images-MAX_NUM_FORMS" id="id_product_images-MAX_NUM_FORMS">
</td></tr>

Adding placeholder to form in Django when using django-filter

I am developing a web application which displays an html table using django-tables2 and which allows filtering this table using django-filter.
In my html template I can simply put {% filter.form %} and I am getting a full form for filtering my table which I think is really great and easy.
However, I would like to add placeholders to this form which would be in plane html (using Bootstrap4) something like this:
<div class="container">
<form>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" class="form-control" id="email"
placeholder="ENTER PLACEHOLDER HERE" name="email">
</div>
</form>
</div>
(example taken form here:https://www.w3schools.com/bootstrap4/tryit.asp?filename=trybs_form_basic&stacked=h and edited)
How can I add a placeholder when working with django-filters and all I am doing is adding % filter.form %} to my template?
Edit:
My question was half complete. The answer given by #schwobaseggl works great for most filters. However, when having a RangeFilter and two fields show up (one for the max value and one for the min value) how to set two separate placeholders? One can put the same placeholder into both fields as explained here: How to put placeholder in Django date range filter.
When you define your Filter, provide the placeholder attribute via the attrs kwarg of the widget constructor:
# ...
from django.forms.widgets import TextInput, ...
class FooFilter(filters.FilterSet):
bar = filters.CharFilter(..., widget=TextInput(attrs={'placeholder': 'baz'}))
from django.forms.widgets import TextInput
class BikesFilter(django_filters.FilterSet):
sell_price = django_filters.NumberFilter()
sell_price__gt = django_filters.NumberFilter(field_name='sell_price', lookup_expr
='gte', label='sell price min', widget=TextInput(attrs={'placeholder': 'min'}))
sell_price__lt = django_filters.NumberFilter(field_name='sell_price', lookup_expr
= 'lte', label='sell price max', widget=TextInput(attrs={'placeholder': 'max'}))
class Meta:
model = Bike
fields = (
'sell_price'
)

Django how to retrieve post data from Modelform and static formfields mixed together

I'm using a Django Modelform mixed with static form fields to generate a HTML form like so:
Models.py:
class example(models.Model):
value_1 = models.PositiveIntegerField()
value_2 = models.PositiveIntegerField(unique=True)
value_3 = models.PositiveIntegerField()
value_4 = models.CharField(max_length=600)
value_5 = models.CharField(max_length=600)
label_1 = models.CharField(max_length=3)
label_2 = models.CharField(max_length=3)
label_3 = models.CharField(max_length=3)
def __str__(self):
return self.name
class ExampleForm(ModelForm):
class Meta:
model = example
fields = '__all__'
exclude = ['label_1','label_2','label_3']
Template (HTML):
<form method="post" action="" enctype="multipart/form-data">
<!-- Start static form fields -->
<input type="checkbox" checked="checked" name ="label_1" value="blabla">
<input type="checkbox" checked="checked" name ="label_2" value="blabla">
<input type="checkbox" checked="checked" name ="label_3" value="blabla">
<!-- End static form fields -->
<!-- Start Modelform fields -->
{% for field in form %}
<div class="form-group form-group-default required">
{{ field.label_tag }}
{{ field|addcss:'form-control' }}
<span class="text-danger">{{ field.errors }}</span>
</div>
{% empty %}
<h3 class="text-warning">Database lookup failed.</h3>
{% endfor %}
<!-- End Modelform fields -->
<button class="btn btn-complete btn-lg" type="submit"><i class="fa fa-save"></i></button>
</form>
The HTML form shows perfectly with the static and the dynamic form fields. I can request the form values and see them like so:
form = ExampleForm(request.POST)
return HttpResponse(form)
Problem:
When I use the above technique to request POST data I only receive the post data from Modelform (dynamic fields) and not the data from the static fields!
I found out I can request the posted values from the static fields this way:
label_1 = request.POST.get("label_1")
return HttpResponse(label_1)
Above code will give me the value of the posted static checkbox. However, now I have to collect all the static fields and I want to catch all posted data at once and than handle it further.
Now my question, how is it possible that I don't get all the post values when I request them in Django? How do I make 1 variable with all the post data in it including modelform values and static form fields?
Hope my question is clear, thanks guys! :)
I believe you might want to take a close look at this line:
exclude = ['label_1','label_2','label_3']

Categories

Resources