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

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")}

Related

Handling Redirect with URL that has multiple PK

I'm new to Django and having trouble redirecting after the AddContactEvent form has been filled out. After submitting the form, here is the redirect error:
No URL to redirect to. Either provide a url or define a
get_absolute_url method on the Model.
I am having trouble figuring out how to redirect it since the AddContactEvent url path('contacts/<int:pk1>/addcontactevent)
only has one pk. In the EventDetail url there are clearly two pk which would have the contact pk and the event pk. The EventDetail page seems to be creating, but I can't get it to redirect to that page due to multiple PK. how would you handle the redirect?
urls.py
path('contacts/<int:pk>', contact_detail.as_view(), name="contact_detail"),
path('contacts/<int:pk1>/addcontactevent', AddContactEvent.as_view(), name="addcontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>/update', UpdateContactEvent.as_view(), name="updatecontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>', EventDetail.as_view(), name="eventdetail"),
views.py
class AddContactEvent(CreateView):
form_class = ContactEventForm
template_name = 'crm/contactevent.html'
def dispatch(self, request, *args, **kwargs):
"""
Overridden so we can make sure the `Ipsum` instance exists
before going any further.
"""
self.contact = get_object_or_404(Contact, pk=kwargs['pk1'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
""" Save the form instance. """
contact = get_object_or_404(Contact, pk=self.kwargs['pk1'])
form.instance.contact = contact
form.instance.created_by = self.request.user
return super().form_valid(form)
class UpdateContactEvent(UpdateView):
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
class DeleteContactEvent(DeleteView):
model = Event
class EventDetail(DetailView):
template_name = 'crm/eventdetail.html'
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
one way to get rid of the error is to define a get absolute url in contact model
def get_absolute_url(self):
return reverse("contact_detail", kwargs={"pk": self.pk})
You have a saved Event object (which has a pk) and you have the contact pk
def get_success_url(self):
return reverse('eventdetail', kwargs={'pk1': self.kwargs['pk1'], 'pk2': self.object.pk})

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

Django generic create view default value for model choice field

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.

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

Passing session data to ModelForm inside of ModelAdmin

I'm trying to initialize the form attribute for MyModelAdmin class inside an instance method, as follows:
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
MyModelAdmin.form = MyModelForm(request.user)
My goal is to customize the editing form of MyModelForm based on the current session. When I try this however, I keep getting an error (shown below). Is this the proper place to pass session data to ModelForm? If so, then what may be causing this error?
TypeError at ...
Exception Type: TypeError
Exception Value: issubclass() arg 1 must be a class
Exception Location: /usr/lib/pymodules/python2.6/django/forms/models.py in new, line 185
Combining the good ideas in Izz ad-Din Ruhulessin's answer and the suggestion by Cikić Nenad, I ended up with a very awesome AND concise solution below:
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.form.request = request #so we can filter based on logged in user for example
return super(CustomModelAdmin, self).get_form(request,**kwargs)
Then just set a custom form for the modeladmin like:
form = CustomAdminForm
And in the custom modelform class access request like:
self.request #do something with the request affiliated with the form
Theoretically, you can override the ModelAdmin's get_form method:
# In django.contrib.admin.options.py
def get_form(self, request, obj=None, **kwargs):
"""
Returns a Form class for use in the admin add view. This is used by
add_view and change_view.
"""
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
fields = None
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(kwargs.get("exclude", []))
exclude.extend(self.get_readonly_fields(request, obj))
# if exclude is an empty list we pass None to be consistant with the
# default on modelform_factory
exclude = exclude or None
defaults = {
"form": self.form,
"fields": fields,
"exclude": exclude,
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
return modelform_factory(self.model, **defaults)
Note that this returns a form class and not a form instance.
If some newbie, as myself, passes here:
I had to define:
class XForm(forms.ModelForm):
request=None
then at the end of the previous post
mfc=modelform_factory(self.model, **defaults)
self.form.request=request #THE IMPORTANT statement
return mfc
i use queryset fot filtering records, maybe this example help you:
.....
.....
def queryset(self, request):
cuser = User.objects.get(username=request.user)
qs = self.model._default_manager.get_query_set()
ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
if ordering:
qs = qs.order_by(*ordering)
qs = qs.filter(creator=cuser.id)
return qs
Here is a production/thread-safe variation from nemesisfixx solution:
def get_form(self, request, obj=None, **kwargs):
class NewForm(self.form):
request = request
return super(UserAdmin, self).get_form(request, form=NewForm, **kwargs)
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
get_form = super(CustomModelAdmin, self).get_form(request,**kwargs)
get_form.form.request = request
return get_form
Now in ModelForm, we can access it by
self.request
Example:
class CustomModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TollConfigInlineForm, self).__init__(*args, **kwargs)
request = self.request
user = request.user