I want to be able to save a form as a draft when it's not completely filled up and also save it as usual with classic django form validation.
To do so, I have two submit buttons in my form and I find out in my post request which button has been clicked :
class MyView(UpdateView):
def post(self, request, *args, **kwargs):
def submit_draft(self, request):
if 'draft' in request.POST:
out = True
else:
out = False
return out
In my models, all my field allow blank fields so saving an incomplete form as draft causes no issue if fields are empty.
I would like to make the form fields required when the form is saved with the normal save action.
One option I have thought of so far but didn't successfully implement :
=> Override get_form function so that when I hit Save as draft it just does it's normal action and when I hit Save, it modifies my fields required attribute.
tldr: I'm looking for a way to do something like that to my form based on the submit button that's been clicked
for field in form:
self.fields[field].required = True
Solved my problem by subclassing my forms as follows :
My form based on my model with null and blank = True
class RegionForm(forms.ModelForm):
class Meta:
model = Region
fields = ['region_name',
'region_description'
]
def __init__(self, *args, **kwargs):
super(RegionForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = True
class RegionDraftForm(RegionForm):
class Meta(RegionForm.Meta):
pass
def __init__(self, *args, **kwargs):
super(RegionDraftForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
That way I can instantiate in my views the form I need to save as draft or to normally save with complete form validation.
A solution I have used in the past is to serialise the content of the form into a "drafts" table when the "save draft" button is clicked.
When a user comes back to editing the form you load the json back into the form for continued editing. This can be done either using javascript or within the django form view.
Some steps you might follow:
When "Save Draft" is clicked, post the form to the draft view.
Serialise the content into json; storing the content in a "drafts model".
When a user click to re-open a draft pull the content from the database, parsing the json back into the form.
When the user clicks "Save" you save the form contents into the database and delete the draft from the "drafts table".
This is an example of what your Drafts model could look like.
class Drafts(models.Model):
user = models.ForeignKey(User)
form_name = models.CharField(max_length=100)
serialised_form = models.TextField() # could use json field here
def to_dict(self):
return json.loads(self.serialised_form)
I see this method having a number of advantages over
Related
So, i have a rather usual "update item" page that is a class-based view which inherits UpdateView. (in views.py it looks like "class ItemUpdateView(UpdateView) and it has method get_success_url(self) defined which contains the redirect url where user will be taken after clicking "Update" button.
My problem is that in my application, there are two different pages that could lead me to this "Update item" page, and depending on the page that user comes from - i want to take the user back to either pageA or pageB upon the successful update of the item.
I wasn't able to find the best-practices of how to handle this anywhere on the web, so - would really appreciate the help.
My guess is that I need to create an additional parameter that will be a part of the url and will contain A or B depending on the pageA or pageB that user came from, i.e. the url itself would be something like '/itemUpdate/int:pk/sourcepage' => '/itemUpdate/45/A'. Does that sound like a correct aproach or is there a better way?
There is a better way that you can check Meta dictionary in request:
write in your views file:
class ItemUpdateView(UpdateView):
previous_url = ''
form_class = UpdateItem
def get(self, request, *args, **kwargs):
self.previous_url = request.META.get('HTTP_REFERER')
print(self.previous_url)
return super().get(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['success_url'] = self.previous_url
return initial
def form_valid(self, form):
self.success_url = form.cleaned_data['success_url']
print(self.success_url)
return super().form_valid(form)
# also you can use get_success_url instead of form_valid()
# def get_success_url(self):
# return super().get_form().cleaned_data['success_url']
and then write a hidden field in your form and name it success_url
class UpdateItem(forms.ModelForm):
success_url = forms.URLField(widget=forms.HiddenInput)
class Meta:
model=Item
fields=['itemName','quantity']
Note you can not use instance in order to get success_url field, because this field belong to form nor your model instance !
refer to documentions
The use case: It's kind of like a signup sheet. The form has 3 fields, each of which should only be editable if they were blank when the form was requested. Once the form is submitted, these fields for this specific instance of the model shouldn't be editable.
The question: How do I do this? I was thinking of using Javascript to set the fields to editable if they already have something in there, but I'm almost certain there's an easier way.
i think the easiest way is to use javascript since it works directly within the browser.
just get the id of your fields and set disabled to true
var myInput = document.getElementById("myFieldId")
if (myInput.value.length != 0) {
myInput.disabled = true
}
You can do a check in the init function of your form and then update the field.
class YourForm(forms.Form):
somefield = forms.Charfield()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.somefield is not "":
self.fields['somefield'].disabled = True
Validation should always be done on the server-side and not rely on the client.
In my Django form, I have one select field that needs to be populated dynamically based on some information about the current user. I am able to get the field set up correctly and rendered in the form - however, I'm getting an error when submitting the form because it's getting hung up on some of the logic that I have in the form's __init__ method that only makes sense in the context of generating the form in the first place. I'm super-new to Django, and I'm not quite familiar with the design principles for this sort of situation.
In my app's admin.py, I have a method that's used for creating a custom view for a data export form - the relevant parts of it are set up like so...
# admin.py
from organizations.models import Organization
from .forms import ExportForm
class SomeModelAdmin(SimpleHistoryAdmin, SoftDeletionModelAdmin):
def export_view(self, request):
authorized_orgs_queryset = Organization.objects.viewable_for_user(request.user).all()
authorized_orgs = [{'id': org.id, 'name': org.name} for org in authorized_orgs_queryset]
context = dict(
self.admin_site.each_context(request),
form = ExportForm({'authorized_orgs': authorized_orgs}),
)
if request.method == 'POST':
form = ExportForm(request.POST)
if form.is_valid():
# do some stuff with the form.cleaned_data and return a .csv file as a response
return response
return TemplateResponse(request, 'export.html', context)
So the current user may be authorized to export data for multiple organizations, and in the form I'd like to present the user with a select element populated with these organizations.
The ExportForm has a number of "fixed" fields that are always the same, and just the one dynamic select element, which is populated by the authorized_orgs arg that I pass to it - it's defined as...
# forms.py
from django import forms
min_year = 1950
export_formats = [
'csv',
'xls',
'xlsx',
'ods',
'json',
]
class ExportForm(forms.Form):
current_year = datetime.datetime.now().year
export_format = forms.ChoiceField(required=True, label='Format', choices=export_format_choices)
apply_date_range = forms.BooleanField(required=False)
year_from = forms.IntegerField(required=False, disabled=True, min_value=min_year, max_value=current_year, initial=current_year)
year_through = forms.IntegerField(required=False, disabled=True, min_value=min_year, max_value=current_year, initial=current_year)
def __init__(self, *args, **kwargs):
super(ExportForm, self).__init__(*args, **kwargs)
authorized_orgs_choices = [(org['id'], org['name']) for org in args[0]['authorized_orgs']]
self.fields['authorized_org'] = forms.ChoiceField(required=False, label='Choose an authorized organization', choices=authorized_orgs_choices)
When I render the form, all is well. However, form submission is where things go awry. Submitting the form produces the error
File "/code/observations/forms.py", line 28, in __init__
authorized_orgs_choices = [(org['id'], org['name']) for org in args[0]['authorized_orgs']]
File "/usr/local/lib/python3.7/site-packages/django/utils/datastructures.py", line 78, in __getitem__
raise MultiValueDictKeyError(key)
django.utils.datastructures.MultiValueDictKeyError: 'authorized_orgs'
Now, I do understand why this is happening - the __init__ is getting the values from the submitted form as its args, which are different from what I've supplied when setting up the form in the first place.
What I don't know is how this sort of thing should typically be handled in Django... how do I make it so that this dynamic field is created correctly when defining the form to be rendered, and that the data is available to me in form.cleaned_data when it's submitted?
Thanks very much for any insight and help.
Aha - found an answer here - django forms post request raising an error on __init__ method
I needed to make sure to pass those values again when handling the form's POST - I also reworked it a bit to make use of kwargs -
# admin.py
if request.method == 'POST':
form = ExportForm(request.POST, authorized_orgs=authorized_orgs)
# forms.py
def __init__(self, *args, **kwargs):
super(ExportForm, self).__init__(*args)
authorized_orgs_choices = [(org['id'], org['name']) for org in kwargs['authorized_orgs']]
self.fields['authorized_org'] = forms.ChoiceField(required=False, label='Choose an authorized organization', choices=authorized_orgs_choices)
i have a very simple model... something like this:
class MachineTransTable(models.Model):
...
file = models.ForeignKey('File', related_name='the_file')
source = models.TextField()
target = models.TextField()
...
What I'd like to do is to have a page where the user has the source on the left (disable), the target on the right (editable) and a submit button to post the edited target text for EACH selected object in the MachineTransTable table. Here are some more information to better understand my request:
A page refers to a single file and there are several (sometimes hundreds) of objects in the MachineTransTable table belonging to the same file
Each time the user edit a single target and hit the submit button for that object, the object is saved/updated (depending on the initial value of the object) in the DB and the the user can continue to edit all the other objects...
At the end of the page there is another submit button which is used to exit from the page at the end of the work (all the objects have been edited/updated). If an object has not been edited/updated, it keeps its original value.
I tried to use a formset but I guess it's not the right choice... This is the file forms.py
class SegmentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SegmentForm, self).__init__(*args, **kwargs)
if self.instance.id:
self.fields['source'].widget.attrs['readonly'] = True
class Meta:
model = MachineTransTable
fields = ['source','target',]
SegmentFormSet = inlineformset_factory(File, MachineTransTable, form=SegmentForm, fields=('source','target'), extra=0)
and the view.py file is:
class CatUpdateView(LoginRequiredMixin,UpdateView):
Model = MachineTransTable
context_object_name = 'file'
template_name = 'app_cat/cat.html'
form_class = SegmentForm
...
def get_context_data(self, **kwargs):
context = super(CatUpdateView, self).get_context_data(**kwargs)
formset = SegmentFormSet(instance=self.get_object())
context['formset_Segment'] = formset
return context
Using this approach, I have a single form and all the related objects are saved/updated at once when the submit button is hit...
What can I do to achieve what I described above? Thanks
I think your choice is correct, you should not use several (hundreds eventually) forms here if the field in the same model. For two reasons:
You have to do a lot of repeat job to write so many forms and that is easy to make mistake and hard to maintain.
You still have to connect the database and update the record no matter how many field had been edit, and they almost efficiency.
But if you really want to do this,you can just use Ajax to post the current parameters name to the api, then update it, For instance, you have a button for target field:
value_in_the_html
Use Ajax to post the field name and value:
$ajax({
url: "api/table_id",
type: "POST",
datatype: "json",
data: {"field": "target", "value": "value_in_the_html"}
});
In view.py:
def UpdateTable(request, id):
field = request.POST.get("field", None)
value = request.POST.get("value", None)
machine = MachineTransTable.objects.filter(id=id).first()
machine.getattr(machine,field) = value
machine.save()
return HttpResponse("Saved!")
Imagine we're developing a message system, and each Message has a foreign key for sender.
We're using ModelForms, and there is a MessageForm that infers its fields from Message.
Of course, we don't want the user to be able to spoof sender by posting a different sender ID.
Therefore, we must exclude sender from ModelForm and fill it from session on post.
Where and how should I assign arbitrary data to ModelForm fields?
In my example, I probably want to access session so we need to access to request as well.
Does this mean the code has to be in the view, right after the form has been created?
How do we assign a form field from code and make sure it overrides POST data?
(Of course, the example is pretty fictional and is here just to illustrate the problem.)
You can just exclude it as you have and then when processing the form in the view do:
obj = form.save(commit=False)
obj.sender = request.user
obj.save()
Alternatively, you can do it in the save method of the form; for this you need to save the request object momentarily in the form. Something like the following:
class MyForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
self._request = request
super(MyForm, self).__init__(*args, **kwargs)
def save(self, commit=False):
obj = super(MyForm, self).save(commit=False)
obj.sender = self._request.user
if commit:
obj.save()
return obj
I prefer the second way myself as it helps encapsulate the logic regarding that model and it's data in one neat place.
Just exclude the sender field from the ModelForm and, when you instantiate the object in the view on the POST endpoint (just before saving), make sure you populate the sender field with the appropriate session or user ID.
When you exclude the field, there is no way to add the field to the post data[1], so a user can't spoof it.
[1] With JavaScript, a &sender=someSenderId could be added in theory, but in your view, you don't need to look for a sender field. It won't be serialized into the ModelForm object.