Django generic create view default value for model choice field - django

I have a view that inherits from the generic CreateView and overrides the get_initial method like so:
class PosterVisualCreateView (ModelFormMixin, generic.edit.CreateView, ObjectClassContextMixin):
model = Poster
valid_message = "Successfully created object."
template_name = "poser/create_poster_visual.html"
def get_form_class (self):
return super(PosterVisualCreateView, self).get_form_class(extra="CreateVisual")
def get_form_kwargs (self):
kwargs = super(PosterVisualCreateView, self).get_form_kwargs()
kwargs.update({
"company": self.request.company
})
return kwargs
def get_context_data (self, **kwargs):
context = super(PosterVisualCreateView, self).get_context_data(**kwargs)
context.update({
"company": self.request.company,
})
return context
def get_initial (self):
initial = super(PosterVisualCreateView, self).get_initial()
initial.update({
"company": self.request.company,
"template": self.request.company.template_set.all().first()
})
return initial
def form_valid(self, form):
success_url = super(PosterVisualCreateView, self).form_valid(form)
attributes = form.instance.create_attributes()
for attribute in attributes:
attribute.poster = form.instance
attribute.save()
form.instance.save()
form.instance.render_html(commit=True)
form.instance.save()
return success_url
#method_decorator(login_required)
def dispatch (self, *args, **kwargs):
return super(PosterVisualCreateView, self).dispatch(*args, **kwargs)
The page renders this form:
class PosterFormCreateVisual (CompanyHiddenForm):
"""Create form for Posters."""
template = fields.ModelChoiceField(widget=forms.RadioSelect, queryset=Template.objects.all())
product = fields.ModelChoiceField(widget=forms.Select, queryset=Product.objects.all(),
required=False)
class Meta:
model = Poster
fields = ("template", "product", "company")
def __init__ (self, *args, **kwargs):
company = kwargs.pop("company", None)
assert company is not None, "Company is required to create attribute form."
super(PosterFormCreateVisual, self).__init__(*args, **kwargs)
self.fields["template"].queryset = company.template_set.all()
self.fields["product"].queryset = company.product_set.all()
The initial value should be the first item in the radio selection for template but this isn't the case, can anyone help me out here?

Try this:
"template": self.request.company.template_set.all().first()
But as far as I understand you original code should work too.
BTW, how you tested the form? By hitting the "Refresh" button or Ctrl-R/F5 key? Some browsers reload page but save the previously selected/entered form values. To check initial values you should reload the form page by clicking on the address bar (or pressing Ctrl-L) and then pressing the Enter key.

Related

django admin how to add context when using custom action button?

I have a custom action button "Add post" for each record in my admin panel. I want to save in context obj.id to use it for a default value in admin:channels_post_add form
class ChannelAdmin(admin.ModelAdmin):
list_display = ['title','username', 'invite_link', 'account_actions']
def account_actions(self, obj):
!!!! I WANT TO ADD CONTEXT HERE!!!!
return format_html('<a class="button" href="{}">Add post</a>', reverse('admin:channels_post_add'))
account_actions.short_description = 'Actions'
account_actions.allow_tags = True
class Meta:
model = Channel
admin.site.register(Channel, ChannelAdmin)
class PostAdmin(admin.ModelAdmin):
list_display = ['text', 'media', 'send_type', 'created']
class Meta:
model = Post
def get_form(self, request, obj=None, **kwargs):
form = super(PostAdmin, self).get_form(request, obj, **kwargs)
try:
post_id = !!! AND USE IT HERE!!!!
form.base_fields['channels'].initial = post_id
except Exception as e:
print(e)
return form
You can add GET parameters to the url.
url = "%s?pid=%s" % (reverse(admin:channels_post_add), obj.id)
And then request.GET.get("pid") in get_form():
class ChannelAdmin(admin.ModelAdmin):
...
def account_actions(self, obj):
url = "%s?pid=%s" % (reverse(admin:channels_post_add), obj.id)
return format_html('<a class="button" href="{}">Add post</a>', url)
class PostAdmin(admin.ModelAdmin):
...
def get_form(self, request, obj=None, **kwargs):
form = super(PostAdmin, self).get_form(request, obj, **kwargs)
try:
form.base_fields['channels'].initial = request.GET.get("pid")
except Exception as e:
print(e)
return form
I think you have to use ModelAdmin.get_changeform_initial_data(request)(Django Docs) to set initial value.
A hook for the initial data on admin change forms. By default, fields
are given initial values from GET parameters. For instance,
?name=initial_value will set the name field’s initial value to be
initial_value.
This method should return a dictionary in the form {'fieldname':
'fieldval'}:
url = "%s?channels=%s" % (reverse(admin:channels_post_add), obj.id)
or
def get_changeform_initial_data(self, request):
return {'channels': request.GET.get("pid")}

Django custom Form and GenericView

The following code is working but I wonder if there is a more elegant way of doing. I have to pass the specie_id so I can filter the breeds to the corresponding specie. I can pass the specie_id to the view but I also have the information in the Resident model ("specie").
both get() and post() have nearly the same code, passing the specie_id.
The View
class ResidentUpdate(UpdateView):
model = Resident
template_name = "administration/resident_form.html"
form_class = ResidentCreateForm
def get(self, request, pk):
initial = self.model.objects.get(id=pk)
form = self.form_class(instance=initial, specie_id=initial.specie.id)
return render(request, self.template_name, {"form": form})
def post(self, request, pk):
initial = self.model.objects.get(id=pk)
form = self.form_class(request.POST, specie_id=initial.specie.id, instance=initial)
if form.is_valid():
form.save()
return redirect("resident_detail", pk)
return render(request, self.template_name, {"form", form})
The Form
class ResidentCreateForm(ModelForm):
class Meta:
model = Resident
fields = [
"name",
"specie",
"breed",
"gender",
"gender_status",
"birth_date",
"organization",
"social_behaviors",
"notes",
]
widgets = {
"birth_date": DateInput(attrs={"class": "flatpickr"}),
"specie": HiddenInput(),
}
def __init__(self, *args, **kwargs):
self.specie_id = kwargs.pop("specie_id", None)
super(ResidentCreateForm, self).__init__(*args, **kwargs)
self.fields["specie"].initial = self.specie_id
self.fields["breed"].queryset = Breed.objects.for_specie(self.specie_id)
EDIT :
#Alasdair's answer is good and I think I perfected it a little more. My form is used for the create view too. So I added a check to see if I have the specie_id in kwargs (create) or if I have to use the specie_id from the instance (update)
def __init__(self, *args, **kwargs):
self.specie_id = kwargs.pop("specie_id", None)
super(ResidentForm, self).__init__(*args, **kwargs)
if not self.specie_id:
self.specie_id = self.instance.specie.id
self.fields["specie"].initial = self.specie_id
self.fields["breed"].queryset = Breed.objects.for_specie(self.specie_id)
It looks like you can do self.fields["breed"].queryset = Breed.objects.for_specie(self.initial.specie_id), then you don't need to pass in specie_id to the form.
class ResidentCreateForm(ModelForm):
class Meta:
model = Resident
fields = [
"name",
"specie",
"breed",
"gender",
"gender_status",
"birth_date",
"organization",
"social_behaviors",
"notes",
]
widgets = {
"birth_date": DateInput(attrs={"class": "flatpickr"}),
}
def __init__(self, *args, **kwargs):
super(ResidentCreateForm, self).__init__(*args, **kwargs)
self.fields["breed"].queryset = Breed.objects.for_specie(self.instance.specie_id)
Note I've removed the specie hidden input above, I don't think it's necessary.
The UpdateView takes care of passing instance to the form, so you can simplify the view.
from django.urls import reverse
class ResidentUpdate(UpdateView):
model = Resident
template_name = "administration/resident_form.html"
form_class = ResidentCreateForm
def get_success_url(self):
"""Redirect to resident_detail view after a successful update"""
return reverse('resident_detail', args=[self.kwargs['pk']]
I think a better approach would be overriding the get_form_kwargs method in your views.
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
form_kwargs.update({'specie_id': self.kwargs.get('specie_id')})
return form_kwargs

Passing and accessing value in Django form

I'm trying to pass info to my form and I have a bit of a struggle with that. My code looks as follows:
views.py
class ObjectUpdateView(UpdateView):
template_name = 'manage/object_form.html'
form_class = ObjectEditForm
def get_success_url(self):
#...
def form_valid(self, form):
return super(ObjectUpdateView, self).form_valid(form)
def get_object(self):
return get_object_or_404(Room, pk=self.kwargs['object_id'])
def get_form_kwargs(self, **kwargs):
objectid = self.kwargs['object_id']
object = Object.objects.get(id = objectid)
container = object.container
kwargs['container_id'] = container.id
return kwargs
forms.py
class ObjectEditForm(forms.ModelForm):
class Meta:
model = Object
fields = ['TestField']
def __init__(self, *args, **kwargs):
super(ObjectEditForm, self).__init__(*args, **kwargs)
self.Container_id = kwargs.pop('container_id')
form_page.html
{{fomr.kwarg.Container_id}}
As you can see I'd like to access Container_id value in my form_page.html. Unfortunately, nothing is there. What I also noticed, that with __init__ I had to add, now values are empty in my form. Before I added __init__ all values were properly passed (well, except Container_id).
Could you recommend how I can pass such value to be accessed in the form template?
You can render this with:
{{ form.Container_id }}
In your form you should first pop the container_id from the kwargs, like:
class ObjectEditForm(forms.ModelForm):
class Meta:
model = Object
fields = ['TestField']
def __init__(self, *args, **kwargs):
# first pop from the kwargs
self.Container_id = kwargs.pop('container_id', None)
super(ObjectEditForm, self).__init__(*args, **kwargs)
Use the context over the form
That being said, it is a bit strange that you pass this to the form, and not add this to the context data. You can simplify your view a lot to:
class ObjectUpdateView(UpdateView):
template_name = 'manage/object_form.html'
pk_url_kwarg = 'object_id'
form_class = ObjectEditForm
def get_success_url(self):
#...
def get_context_data(self, **kwargs):
objectid = self.kwargs['object_id']
object = Object.objects.get(id = objectid)
context = super().get_context_data()
context.update(container_id=object.container_id)
return context
Django automatically fetches a single element based on the pk_url_kwarg [Django-doc]. You only need to set it correctly, so here that is the object_id.
In that case, we can simply render this with:
{{ container_id }}
and you do not need to store this in the form.

Django Form Wizard Dynamic Form Not Saving Data

Currently Happening:
Dynamically generated form and form fields are being displayed.
Enter some data into the said fields, but self.get_all_cleaned_data() returns nothing.
Form returns to page 0 instead of submitting the form and using done()
What I want to happen:
- Data in fields to be retained and displayed when going back, or to the confirmation page
- Form to actually submit and use done() to process and save
The following the my forms.py
class OrderForm(forms.Form):
class Meta:
localized_fields = ('__all__',)
def __init__(self, *args, **kwargs):
self.fields = kwargs.pop('fields')
fields = self.fields
super(OrderForm, self).__init__(*args, **kwargs)
if not isinstance(fields, str):
for i in fields.fields.all():
widget = forms.TextInput()
_type = forms.CharField
if i.field_type == Field.TEXTAREA_FIELD:
widget = forms.Textarea
...
self.fields[i.name] = _type(**fields)
This is supposed to get Database created forms and field data and generate fields accordingly. For example:
Form A has fields:
Name (Regular Text Field)
Address (Textarea)
The above code will then generate fields for those.
The next block of code is from my views.py file
FORM_TEMPLATES = {
"0": 'order/details.html',
"1": 'order/details.html',
"2": 'order/details.html',
"3": 'order/details.html',
"4": 'order/details.html',
"confirm": 'order/confirm.html',
}
class Order(SessionWizardView):
form_list = [OrderForm]
def get_current_step_form(self, company, *args, **kwargs):
step_form = [Form.objects.all()]
step_form.append('Confirm')
return step_form
def get_context_data(self, form, **kwargs):
context = super(Order, self).get_context_data(form=form, **kwargs)
# Returns {}, but I want this to return all previous field values
context.update({
'all_data': self.get_all_cleaned_data(),
})
return context
def post(self, *args, **kwargs):
go_to_step = self.request.POST.get('wizard_goto_step', None)
form = self.get_form(data=self.request.POST)
current_index = self.get_step_index(self.steps.current)
goto_index = self.get_step_index(go_to_step)
if current_index > goto_index:
self.storage.set_step_data(self.steps.current,
self.process_step(form))
self.storage.set_step_files(self.steps.current,
self.process_step_files(form))
return super(Order, self).post(*args, **kwargs)
def get_form(self, step=None, data=None, files=None):
"""
Get the form and add to form_list
"""
form = super(Order, self).get_form(step, data, files)
company = ...
get_forms = self.get_current_step_form(company=company)
form_list_value = dict(self.form_list)['0']
while len(self.form_list.items()) < len(get_forms):
self.form_list.update({str(len(self.form_list.items())): form_list_value})
return form
def done(self, form_list, **kwargs):
return HttpResponse("View")
done() is a work in progress, but it doesn't even seem to reach that point, as it keeps going from (for example) Form 0-1-2-3-0-...
The confirm form will not have any field values form the previous pages and will only return {}
Any help would be appreciated,
Thanks

Filtering a model in a CreateView with get_queryset

I'm trying to filter a model with get_queryset() and it seems to work in the view but not in the template.
My view :
class FolderCreate(CreateView):
fields = ['name', 'parent']
template_name = 'Form/folder_create.html'
def get_queryset(self):
folders = Folder.objects.filter(owner=self.request.user)
print folders # ==> return [<Folder: Folder>, <Folder: Another folder>]
return folders
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.owner = self.request.user
return super(FolderCreate, self).form_valid(form)
def get_initial(self):
if self.request.method == 'GET':
foldersUrl = self.request.META['HTTP_REFERER'].split('/')
foldersUrl.pop()
folder = urllib2.unquote(foldersUrl[-1])
try:
return {'parent' : Folder.objects.get(name=folder, owner=self.request.user)}
except Folder.DoesNotExist:
pass
As you can see, folders return two objects related to the session user in get_queryset() : 'Folder' and 'Another folder
Infortunately, the combobox of my template get all the folders, without any filtering.
Any idea ?
The issue here is that get_queryset is not used in a CreateView, as it's meant for filtering the models returned for display in a list or detail view. You want something completely different: you want to filter the choices available in a form field.
To do that you will need to create a custom ModelForm that accepts a user kwarg and filters the queryset accordingly:
class FolderForm(forms.ModelForm):
class Meta:
model = Folder
fields = ['name', 'parent']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(FolderForm, self).__init__(*args, **kwargs)
self.fields['parent'].queryset = Folder.objects.filter(user=user)
and then change your view to use that form and pass in the user parameter:
class FolderCreate(CreateView):
template_name = 'Form/folder_create.html'
form_class = FolderForm
def get_form_kwargs(self):
kwargs = super(FolderCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs